diff options
author | heraklit256 <37872459+heraklit256@users.noreply.github.com> | 2018-08-07 14:17:55 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-08-07 14:17:55 +0000 |
commit | d0c411efd03829a3caa71fca55170136531f0fb2 (patch) | |
tree | f8208bdc181e952105e3ed7344b8e8256f0cce60 | |
parent | 728b3a44311cab0717fc1ef9d1576ed94f7118e4 (diff) | |
parent | 043e80725dbacdae4c9589e2558f4b33faf5776a (diff) | |
download | rspamd-d0c411efd03829a3caa71fca55170136531f0fb2.tar.gz rspamd-d0c411efd03829a3caa71fca55170136531f0fb2.zip |
Merge pull request #3 from vstakhov/master
merge upstream into local master
227 files changed, 15573 insertions, 8833 deletions
diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 000000000..223ae76cf --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,150 @@ +references: + - &defaults + docker: + - image: circleci/ruby:latest + + - &workspace_root + ~/ + + - &capture_coverage_data + run: + name: Capturing coverage data + command: | + set -e + sudo apt-get install -qq lcov + gem install coveralls-lcov + lcov --no-external -b ../project -d ../project -c --output-file coverage.${CIRCLE_JOB}.info + + - &restore_coverage_data + restore_cache: + keys: + - coverage-{{ .Environment.CIRCLE_WORKFLOW_ID }} + + - &merge_and_upload_coverage_data + run: + name: Merging and uploading coverage data + command: | + set -e + if [ -f ~/project/coverage.rspamd-test.info ] && [ -f ~/project/coverage.functional.info ]; then + sudo apt-get install -qq lcov + lcov -a ~/project/coverage.rspamd-test.info -t rspamd-test -a ~/project/coverage.functional.info -t functional -o coverage.info + gem install coveralls-lcov + if [ ! -z $COVERALLS_REPO_TOKEN ]; then coveralls-lcov -t ${COVERALLS_REPO_TOKEN} coverage.info || true; fi + fi + +version: 2 +jobs: + build: + <<: *defaults + steps: + - checkout + + - run: sudo apt-get update -qq || true + - run: sudo apt-get install -qq cmake libevent-dev libglib2.0-dev libicu-dev libluajit-5.1-dev libmagic-dev libsqlite3-dev libssl-dev ragel + + - run: mkdir ../build ; mkdir ../install ; cd ../build + - run: cmake ../project -DDBDIR=/nana -DENABLE_COVERAGE=ON -DCMAKE_INSTALL_PREFIX=../install + + - run: make install -j`nproc` + + - persist_to_workspace: + root: *workspace_root + paths: + - project + - build + - install + + rspamd-test: + <<: *defaults + steps: + - attach_workspace: + at: *workspace_root + + - run: sudo apt-get update -qq || true + - run: sudo apt-get install -qq cmake libluajit-5.1-dev libmagic-dev + + - run: cd ../build + - run: make rspamd-test -j`nproc` + - run: set +e; test/rspamd-test -p /rspamd/lua; echo "export RETURN_CODE=$?" >> $BASH_ENV + + - *capture_coverage_data + + # Share coverage data between jobs + - save_cache: + key: coverage-{{ .Environment.CIRCLE_WORKFLOW_ID }} + paths: + - coverage.rspamd-test.info + - *restore_coverage_data + + - *merge_and_upload_coverage_data + + - run: (exit $RETURN_CODE) + + functional: + <<: *defaults + steps: + - attach_workspace: + at: *workspace_root + + - run: sudo apt-get update -qq || true + - run: sudo apt-get install -qq libluajit-5.1-dev libpcre3-dev luarocks opendkim-tools python-pip redis-server + + - run: sudo pip install demjson psutil robotframework + - run: sudo luarocks install luacheck + + - run: cd ../build + - run: set +e; RSPAMD_INSTALLROOT=../install sudo -E robot -x xunit.xml --exclude isbroken ../project/test/functional/cases; echo "export RETURN_CODE=$?" >> $BASH_ENV + + - *capture_coverage_data + + # Share coverage data between jobs + - save_cache: + key: coverage-{{ .Environment.CIRCLE_WORKFLOW_ID }} + paths: + - coverage.functional.info + - *restore_coverage_data + + - *merge_and_upload_coverage_data + + - store_artifacts: + path: output.xml + - store_artifacts: + path: log.html + - store_artifacts: + path: report.html + + - run: mkdir -p test-results; mv xunit.xml test-results + - store_test_results: + path: test-results + + - run: (exit $RETURN_CODE) + + eslint: + docker: + - image: circleci/node:latest + steps: + - checkout + - restore_cache: + keys: + - v1-dependencies-{{ checksum "package.json" }} + # fallback to using the latest cache if no exact match is found + - v1-dependencies- + - run: npm install + - save_cache: + paths: + - node_modules + key: v1-dependencies-{{ checksum "package.json" }} + - run: ./node_modules/.bin/eslint -v && ./node_modules/.bin/eslint ./ + +workflows: + version: 2 + build-and-test: + jobs: + - build + - eslint + - rspamd-test: + requires: + - build + - functional: + requires: + - build diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 000000000..28c31ce80 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,77 @@ +{ + "env": { + "browser": true + }, + "extends": "eslint:all", + "globals": { + "define": false + }, + "rules": { + "array-bracket-newline": ["error", "consistent"], + "brace-style": ["error", "1tbs", { "allowSingleLine": true }], + "camelcase": "off", + "capitalized-comments": "off", + "comma-dangle": ["error", "only-multiline"], + "curly": ["error", "multi-line"], + "dot-location": ["error", "property"], + "func-names": "off", + // "func-style": ["error", "declaration"], + "id-length": ["error", { "min": 1 }], + // "max-len": ["error", { "code": 120 }], + "max-params": ["warn", 9], + "max-statements": ["warn", 25], + "max-statements-per-line": ["error", { "max": 2 }], + "multiline-comment-style": "off", + "multiline-ternary": ["error", "always-multiline"], + "newline-per-chained-call": ["error", { "ignoreChainWithDepth": 5 }], + "no-extra-parens": ["error", "functions"], + "no-implicit-globals": "off", + "no-magic-numbers": "off", + "no-negated-condition": "off", + "no-plusplus": "off", + "no-ternary": "off", + "no-var": "off", + "object-curly-newline": ["error", { "consistent": true }], + "object-property-newline": ["error", { "allowAllPropertiesOnSameLine": true }], + "object-shorthand": "off", + "one-var": "off", + "padded-blocks": "off", + "prefer-arrow-callback": "off", + "prefer-destructuring": "off", + "prefer-template": "off", + "quote-props" : ["error", "consistent-as-needed"], + "require-jsdoc": "off", + "space-before-function-paren": ["error", { + "anonymous": "always", + "named": "never" + }], + "vars-on-top": "off", + + + // Temporarily disabled rules + "array-callback-return": "off", + "array-element-newline": "off", + "callback-return": "off", + "consistent-return": "off", + "consistent-this": "off", + "default-case": "off", + "func-style": "off", + "function-paren-newline": "off", + "global-require": "off", + "guard-for-in": "off", + "init-declarations": "off", + "key-spacing": "off", + "line-comment-position": "off", + "max-len": "off", + "max-lines": "off", + "max-lines-per-function": "off", + "new-cap": "off", + "no-inline-comments": "off", + "no-invalid-this": "off", + "no-underscore-dangle": "off", + "one-var-declaration-per-line": "off", + "prefer-spread": "off", + "sort-keys": "off", + "sort-vars": "off" + } +} diff --git a/.luacheckrc b/.luacheckrc index 8a3240b12..324c45985 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -28,6 +28,7 @@ globals = { 'rspamd_maps', 'rspamd_plugins_state', 'rspamadm', + 'loadstring' } ignore = { diff --git a/CMakeLists.txt b/CMakeLists.txt index 80a804e2e..80aad7f69 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,8 +11,8 @@ PROJECT(rspamd C) ENABLE_LANGUAGE(ASM) SET(RSPAMD_VERSION_MAJOR 1) -SET(RSPAMD_VERSION_MINOR 7) -SET(RSPAMD_VERSION_PATCH 6) +SET(RSPAMD_VERSION_MINOR 8) +SET(RSPAMD_VERSION_PATCH 0) # Keep two digits all the time SET(RSPAMD_VERSION_MAJOR_NUM ${RSPAMD_VERSION_MAJOR}0) @@ -46,8 +46,6 @@ OPTION(ENABLE_GPERF_TOOLS "Enable google perftools [default: OFF]" OPTION(ENABLE_STATIC "Enable static compiling [default: OFF]" OFF) OPTION(ENABLE_LUAJIT "Link with libluajit [default: ON]" ON) OPTION(ENABLE_DB "Find and link with DB library [default: OFF]" OFF) -OPTION(ENABLE_SQLITE "Find and link with sqlite3 library [default: OFF]" OFF) -OPTION(ENABLE_HIREDIS "Find and link with external redis library [default: ON]" ON) OPTION(ENABLE_URL_INCLUDE "Enable urls in ucl includes (requires libcurl or libfetch) [default: OFF]" OFF) OPTION(NO_SHARED "Build internal libs static [default: ON]" ON) OPTION(INSTALL_EXAMPLES "Install examples [default: OFF]" OFF) @@ -64,6 +62,7 @@ OPTION(ENABLE_COVERAGE "Build rspamd with code coverage options [default: OF OPTION(ENABLE_FULL_DEBUG "Build rspamd with all possible debug [default: OFF]" OFF) OPTION(ENABLE_UTILS "Build rspamd internal utils [default: OFF]" OFF) OPTION(ENABLE_TORCH "Install torch7 with Rspamd [default: ON]" ON) +OPTION(ENABLE_LIBUNWIND "Use libunwind to print crash traces [default: OFF]" OFF) INCLUDE(FindArch.cmake) TARGET_ARCHITECTURE(ARCH) @@ -534,6 +533,9 @@ IF(CMAKE_SYSTEM_NAME MATCHES "^.*BSD$|DragonFly") CHECK_FUNCTION_EXISTS(pidfile_open HAVE_PIDFILE) CHECK_FUNCTION_EXISTS(pidfile_fileno HAVE_PIDFILE_FILENO) ENDIF() + IF(CMAKE_SYSTEM_NAME MATCHES "^NetBSD$") + LIST(APPEND CMAKE_REQUIRED_LIBRARIES rt) + ENDIF() SET(POE_LOOP "Loop::Kqueue") SET(TAR "gtar") ENDIF() @@ -630,6 +632,12 @@ IF(ENABLE_JEMALLOC MATCHES "ON") SET(WITH_JEMALLOC "1") LIST(APPEND RSPAMD_REQUIRED_LIBRARIES "-lpthread") ENDIF() + +IF(ENABLE_LIBUNWIND MATCHES "ON") + ProcessPackage(LIBUNWIND LIBRARY unwind INCLUDE libunwind.h INCLUDE_SUFFIXES include/libunwind + ROOT ${LIBUNWIND_ROOT_DIR} MODULES libunwind) + SET(WITH_LIBUNWIND "1") +ENDIF() ProcessPackage(GTHREAD2 LIBRARY gthread-2.0 INCLUDE glib.h INCLUDE_SUFFIXES include/glib include/glib-2.0 ROOT ${GLIB_ROOT_DIR} MODULES gthread-2.0>=2.28) @@ -666,8 +674,8 @@ ProcessPackage(LIBZ LIBRARY z INCLUDE zlib.h INCLUDE_SUFFIXES include/zlib IF(ENABLE_HYPERSCAN MATCHES "ON") ProcessPackage(HYPERSCAN LIBRARY hs INCLUDE hs.h INCLUDE_SUFFIXES - include/hyperscan include/hs - ROOT ${HYPERSCAN_ROOT_DIR} MODULES hs) + hs include/hs + ROOT ${HYPERSCAN_ROOT_DIR} MODULES libhs) SET(WITH_HYPERSCAN 1) ENDIF() IF (ENABLE_FANN MATCHES "ON") @@ -712,7 +720,7 @@ IF(ENABLE_GPERF_TOOLS MATCHES "ON") ENDIF(ENABLE_GPERF_TOOLS MATCHES "ON") IF (ENABLE_HYPERSCAN MATCHES "ON") - IF(${HYPERSCAN_LIBRARY} MATCHES ".*[.]a$") + IF(${HYPERSCAN_LIBRARY} MATCHES ".*[.]a$" OR STATIC_HYPERSCAN) ENABLE_LANGUAGE(CXX) SET(USE_CXX_LINKER 1) ENDIF() @@ -899,6 +907,7 @@ CHECK_INCLUDE_FILES(cpuid.h HAVE_CPUID_H) CHECK_INCLUDE_FILES(dirent.h HAVE_DIRENT_H) CHECK_INCLUDE_FILES(stropts.h HAVE_STROPS_H) CHECK_INCLUDE_FILES(sys/ioctl.h HAVE_SYS_IOCTL_H) +CHECK_INCLUDE_FILES(ucontext.h HAVE_UCONTEXT_H) # Check platform API CHECK_FUNCTION_EXISTS(setproctitle HAVE_SETPROCTITLE) @@ -984,6 +993,7 @@ LIST(APPEND CMAKE_REQUIRED_INCLUDES "${LIBSSL_INCLUDE}") CHECK_SYMBOL_EXISTS(SSL_set_tlsext_host_name "openssl/ssl.h" HAVE_SSL_TLSEXT_HOSTNAME) CHECK_SYMBOL_EXISTS(dirfd "sys/types.h;unistd.h;dirent.h" HAVE_DIRFD) CHECK_SYMBOL_EXISTS(fpathconf "sys/types.h;unistd.h" HAVE_FPATHCONF) +CHECK_SYMBOL_EXISTS(sigaltstack "signal.h" HAVE_SIGALTSTACK) IF(ENABLE_PCRE2 MATCHES "ON") IF(HAVE_PCRE_JIT) @@ -1247,9 +1257,7 @@ ADD_SUBDIRECTORY(contrib/t1ha) IF (ENABLE_SNOWBALL MATCHES "ON") LIST(APPEND RSPAMD_REQUIRED_LIBRARIES stemmer) ENDIF() -IF(ENABLE_HIREDIS MATCHES "ON") - LIST(APPEND RSPAMD_REQUIRED_LIBRARIES rspamd-hiredis) -ENDIF() +LIST(APPEND RSPAMD_REQUIRED_LIBRARIES rspamd-hiredis) IF(ENABLE_HYPERSCAN MATCHES "OFF") LIST(APPEND RSPAMD_REQUIRED_LIBRARIES rspamd-actrie) ENDIF() @@ -1,3784 +1,3969 @@ +1.7.9: 01 Aug 2018 + * [CritFix] Fix caseless comparison of equal length strings + * [Feature] Add HTTP basic auth support to elastic and clickhouse plugins + * [Feature] Add SPF selector to reputation + * [Feature] Add support of the fallback backends for HTTP maps + * [Feature] Allow to print full mime structure when extracting mime data + * [Feature] Allow to split symbols in reputation plugin + * [Feature] Check attachments only on AV scanners in attachments_only mode + * [Feature] Disable all SSL checks if ssl_no_verify flag is set + * [Feature] Implement parsing of scoped IPv6 addresses + * [Feature] Improve `rspamc counters` output + * [Fix] Add sanity checks when expanding SPF macros + * [Fix] Allow to parse SA rules with no spaces around =~ (dirty hack) + * [Fix] Avoid one extra byte writing + * [Fix] Deal with direct hash table + * [Fix] Detect empty text part as text, not HTML + * [Fix] Do not reduce map watch timeout for mixed http/file maps + * [Fix] Fix HTML part detection heuristic + * [Fix] Fix double free in redirectors cleanup + * [Fix] Fix legacy history handling in the controller + * [Fix] Fix messages insertion + * [Fix] Fix sending string method + * [Fix] Fix statconver command line arguments + * [Fix] Fixed argument checking for being null + * [Fix] Fixed issues reported by luacheck + * [Fix] Freeze updates queue when do actual storage update + * [Fix] HTTP map hash is per-backend and not per-map + * [Fix] Plug memory leak in fuzzy updates + * [Fix] Prefer 'MTA-Name' when producing authentication results + * [Fix] Replace bad unicode sequences instead of stopping on them + * [Fix] Set classifier version on learning + * [Project] Reworked ratelimits + * [Project] Apply topological sorting for symbols in Rspamd + * [Project] Remove global contexts from C modules + * [Project] Move performance critical hash tables to khash + * [WebUI] Avoid unused indexes + * [WebUI] Do not execute `on_success` callback + * [WebUI] Fix history reset for "All SERVERS" (#2346) + * [WebUI] Fix query URL for selected server + * [WebUI] Fix symbols display in legacy history, + * [WebUI] Hide symbols order selector for legacy history + * [WebUI] Refactor query functions into one + * [WebUI] Remove previously-attached event handlers + * [WebUI] Save symbols to the selected server + * [WebUI] Unify arguments of query functions + * [WebUI] Use common query functions to get graph data + * [WebUI] Use common query functions to save symbols + +1.7.8: 12 Jul 2018 + * [Feature] Add more extended statistics about fuzzy updates + * [Feature] Add more non-conformant Received headers support + * [Feature] Add preliminary function to get fuzzy hashes from text in Lua + * [Feature] Allow to configure AV module rejection message + * [Feature] Implement fuzzy hashes extraction in mime tool + * [Feature] Improve WHITE_ON_WHITE rule + * [Feature] Improve integer -> string conversion + * [Feature] Reuse maps in multimap module more aggressively + * [Fix] Avoid race condition in skip map as pool lifetime is not enough + * [Fix] Eliminate all specific C plugins pools + * [Fix] Fix DKIM check rule if DNS is unavailable + * [Fix] Fix build where ucontext is defined in ucontext.h + * [Fix] Fix crash in base url handling + * [Fix] Fix descriptors leak in sqlite3 locking code + * [Fix] Fix messages quarantine + * [Fix] Fix padded numbers printing + * [Fix] Fix race condition on maps reinit + * [Fix] Fix regexp functions when no data is passed + * [Fix] Fix specific urls extraction + * [Fix] Fix styles propagation + * [Fix] Improve resetting of the limit buckets + * [Fix] Initialize sqlite3 properly + * [Fix] Work with broken resolvers in resolv.conf + * [Project] Implement HTTP maps caching + * [Project] Refresh fuzzy hashes when matched + * [Project] Add logic to deduplicate fuzzy updates queue + * [WebUI] Add missed declarations + * [WebUI] Avoid using "undefined" property + * [WebUI] Do not accept passwords containing control characters + * [WebUI] Do not redeclare variables + * [WebUI] Enable strict mode, + * [WebUI] Fix variable assignment + * [WebUI] Initialize variables at declaration + * [WebUI] Remove duplicated path from RequireJS config + * [WebUI] Remove unused block + * [WebUI] Remove unused variable + * [WebUI] Remove unused variables + * [WebUI] Use self-explanatory notation + * [WebUI] Use type-safe equality operators + +1.7.7: 02 Jul 2018 + * [CritFix] Check NM part of pubkey to match it with rotating keypairs + * [CritFix] Do not overwrite PID of the main process + * [CritFix] Fix maps after reload + * [CritFix] Fix maps race conditions on reload + * [CritFix] Fix shmem leak in encrypting proxy mode + * [Feature] Add a concept of ignored symbols to avoid race conditions + * [Feature] Add ability to print bayes tokens in rspamadm mime + * [Feature] Add method to get statistical tokens in Lua API + * [Feature] Add preliminary mime stat command + * [Feature] Add rspamadm mime tool + * [Feature] Add urls extraction tool + * [Feature] Address ZeroFont exploit + * [Feature] Allow rspamadm mime to process multiple files + * [Feature] Allow to extract words in `rspamadm mime` + * [Feature] Allow to print mime part data + * [Feature] Allow to show HTML structure on extraction + * [Feature] Distinguish IP failures from connection failures + * [Feature] Improve output for mime command + * [Feature] Improve styles propagation + * [Feature] Main process crash will now cleanup all children + * [Feature] Preload file and static maps in main process + * [Feature] Print stack trace on crash + * [Feature] Process font size in HTML parser + * [Feature] Propagate content length of invisible tags + * [Feature] Read ordinary file maps in chunks to be more safe on rewrites + * [Feature] Support base tag in HTML + * [Feature] Support more size suffixes when parsing HTML styles + * [Feature] Support opacity style + * [Fix] Another fix for nested composites + * [Fix] Fill nm id in keypairs cache code + * [Fix] Fix colors alpha channel handling + * [Fix] Fix destruction logic + * [Fix] Fix double free + * [Fix] Fix maps preload logic + * [Fix] Fix nested composites process + * [Fix] Fix proxying of Exim connections + * [Fix] Fix reload crash + * [Fix] Fix rspamadm -l command + * [Fix] Update ed25519 signing schema + * [WebUI] Stop using "const" declaration + * [WebUI] Update RequireJS to 2.3.5 + +1.7.6: 15 Jun 2018 + * [CritFix] Fix multiple neural networks support + * [Feature] Add decryption function to keypair command + * [Feature] Add gzip compression for HTTP requests in elastic module + * [Feature] Add gzip methods to lua util + * [Feature] Add maps based on Top Level Domains + * [Feature] Add pubkey checks for dkim_signing + * [Feature] Add support of fake DNS records + * [Feature] Add tool to encrypt files + * [Feature] Allow to add symbols using settings directly + * [Feature] Allow to match private and public keys for DKIM signatures + * [Feature] Allow to set task flags via settings + * [Feature] Allow to specify fake DNS address from the config + * [Feature] Implement signatures verification using rspamadm keypair + * [Feature] Implement signing using `rspamadm keypair` + * [Feature] Improve error reporting for DKIM key access issues + * [Feature] Provide $HOSTNAME variable in UCL + * [Feature] Rework levenshtein distance computation + * [Feature] Split message parsing and processing + * [Feature] Support ED25519 DKIM signatures + * [Feature] Support encrypted configs in UCL + * [Feature] Suppress duplicate warning on very large radix tries + * [Feature] Use OSB to combine header names + * [Fix] Cleanup maps data on shutdown + * [Fix] Fix '~' behaviour in composites + * [Fix] Fix HTTP maps updates + * [Fix] Fix NIST signatures + * [Fix] Fix RFC822 comments when processing a mime address + * [Fix] Fix double free + * [Fix] Fix dynamic settings application + * [Fix] Fix for CommuniGate Pro maillist + * [Fix] Fix keypair creation method to actually create keypair... + * [Fix] Fix matching patterns with no paths + * [Fix] Fix memory leak in parsing comments + * [Fix] Fix parsing of urls with numeric password + * [Fix] Fix plugins intialisation in configwizard + * [Fix] Fix potential crash on reload + * [Fix] Fix potential race condition for a finished HTTP connections + * [Fix] Fix race-condition leak on processes reload + * [Fix] Fix signing in openssl mode + * [Fix] Free language detector structures + * [Fix] Relax alignment requirements + * [Fix] Send DMARC reports compressed + * [Fix] Try to fix leak in dmarc module + * [Fix] Try to plug memory leak in metric exporter + * [Project] Convert rspamadm subcommands to Lua + * [WebUI] Display smtp sender/recipient in history + * [WebUI] Fix elements disabling in "Symbols" tab + * [WebUI] Limit recipients list in history column to 3 + * [WebUI] Match envelope and mime addresses following in arbitrary order + * [WebUI] Update column header + * [WebUI] Wrap addresses in history + 1.7.5: 18 May 2018 - * [Conf] Add MSBL proposed return codes - * [Conf] Add additional groups for policies - * [CritFix] Do not use volatile Lua strings as UCL keys - * [Feature] Add ability to add fuzzy hashes to headers - * [Feature] Add function to extract most meaningful urls - * [Feature] Add rule to block mixed text and encrypted parts - * [Feature] Allow multiple groups for symbols - * [Feature] Allow to disable lua squeezing logic - * [Feature] Allow to get multipart children in Lua - * [Feature] Allow to insert multiple headers from milter headers - * [Feature] Allow to print scores in subject and further extensions - * [Feature] Be more error-prone in squeezed rules - * [Feature] Support multiple return codes in emails module - * [Feature] Use EMA for calculating averages - * [Feature] Use common jit cache for all regexps - * [Feature] support for CommuniGate Pro self-generated messages - * [Fix] Allow to have multiple values for headers as arrays - * [Fix] Do not open sockets for disabled workers - * [Fix] Fix AuthservId - * [Fix] Fix base64 folding in Lua API - * [Fix] Fix build on non-x86 platforms - * [Fix] Fix cached maps logic - * [Fix] Fix compatibility with old maps query logic - * [Fix] Fix crash if skip_map is used - * [Fix] Fix importing static maps from UCL - * [Fix] Fix parsing of unix sockets - * [Fix] Fix raw_mime regexp on HTML part with no text content - * [Fix] Fix tables logging - * [Fix] Fix vertical tab handling in libucl - * [Fix] Try to fix frequency counters - * [Fix] Use better sharding for ip_score - * [Fix] Use multiple results from SURBL DNS reply - * [Fix] When doing AV scan select a different server for retransmit + * [Conf] Add MSBL proposed return codes + * [Conf] Add additional groups for policies + * [CritFix] Do not use volatile Lua strings as UCL keys + * [Feature] Add ability to add fuzzy hashes to headers + * [Feature] Add function to extract most meaningful urls + * [Feature] Add rule to block mixed text and encrypted parts + * [Feature] Allow multiple groups for symbols + * [Feature] Allow to disable lua squeezing logic + * [Feature] Allow to get multipart children in Lua + * [Feature] Allow to insert multiple headers from milter headers + * [Feature] Allow to print scores in subject and further extensions + * [Feature] Be more error-prone in squeezed rules + * [Feature] Support multiple return codes in emails module + * [Feature] Use EMA for calculating averages + * [Feature] Use common jit cache for all regexps + * [Feature] support for CommuniGate Pro self-generated messages + * [Fix] Allow to have multiple values for headers as arrays + * [Fix] Do not open sockets for disabled workers + * [Fix] Fix AuthservId + * [Fix] Fix base64 folding in Lua API + * [Fix] Fix build on non-x86 platforms + * [Fix] Fix cached maps logic + * [Fix] Fix compatibility with old maps query logic + * [Fix] Fix crash if skip_map is used + * [Fix] Fix importing static maps from UCL + * [Fix] Fix parsing of unix sockets + * [Fix] Fix raw_mime regexp on HTML part with no text content + * [Fix] Fix tables logging + * [Fix] Fix vertical tab handling in libucl + * [Fix] Try to fix frequency counters + * [Fix] Use better sharding for ip_score + * [Fix] Use multiple results from SURBL DNS reply + * [Fix] When doing AV scan select a different server for retransmit 1.7.4: 01 May 2018 - * [Conf] Major stock config updates: - - Workers are now specified in a new format worker "type" { ... } - - Enable fuzzy worker to simplify local fuzzy storages configuration - - Bind all workers to localhost by default to avoid security flaws - * [Conf] Make more sane fuzzy_check default settings - * [CritFix] Fix ucl escape for bad symbols - * [Feature] Add failure symbol for AV module - * [Feature] Add lazy expiration mode for new classifier schema - * [Feature] Add preliminary version of maps stats plugin - * [Feature] Allow to block fuzzy requests from specific networks - * [Feature] Allow to change `expire` of live statistics - * [Feature] Distinguish AV failure from clean result - * [Feature] Further improvements of language detector by using khash - * [Feature] Further optimization of the lang_detection - * [Feature] Implement cluster-aware bayes expiry - * [Feature] Implement exclude patterns in rspamc - * [Feature] Implement glob maps in addition to regexp maps - * [Feature] Implement map statistics function for lua API - * [Feature] Implement stop symbols for Clickhouse collection - * [Feature] Support recipients separated by commas - * [Feature] Try harder to upload scripts to the Redis server - * [Feature] Upgrade t1ha distribution - * [Feature] use_domain_sign_inbound - * [Feature] Use scores from maps if `symbols_set` is not defined - * [Fix] Add resolving version of radix map helper - * [Fix] Check URL before adding implicit prefix - * [Fix] Do not check pid/state when using PRNG - * [Fix] Fix CentOS logrotate script for systemd - * [Fix] Fix slash + dot in urls - * [Fix] Fix systemd version of the logrotate script - * [Fix] Propagate key when import implicit array from Lua - * [Fix] Strip spaces from map keys and values - * [Fix] Try to fix a specific case when processing milter protocol - * [Fix] Try to fix crash when a tcp connection cannot be set - * [Fix] Typo use_domain_local --> use_domain_sign_local - * [Fix] Various fixes to once_received module - * [Project] Store hits counters for map elements + * [Conf] Major stock config updates: + - Workers are now specified in a new format worker "type" { ... } + - Enable fuzzy worker to simplify local fuzzy storages configuration + - Bind all workers to localhost by default to avoid security flaws + * [Conf] Make more sane fuzzy_check default settings + * [CritFix] Fix ucl escape for bad symbols + * [Feature] Add failure symbol for AV module + * [Feature] Add lazy expiration mode for new classifier schema + * [Feature] Add preliminary version of maps stats plugin + * [Feature] Allow to block fuzzy requests from specific networks + * [Feature] Allow to change `expire` of live statistics + * [Feature] Distinguish AV failure from clean result + * [Feature] Further improvements of language detector by using khash + * [Feature] Further optimization of the lang_detection + * [Feature] Implement cluster-aware bayes expiry + * [Feature] Implement exclude patterns in rspamc + * [Feature] Implement glob maps in addition to regexp maps + * [Feature] Implement map statistics function for lua API + * [Feature] Implement stop symbols for Clickhouse collection + * [Feature] Support recipients separated by commas + * [Feature] Try harder to upload scripts to the Redis server + * [Feature] Upgrade t1ha distribution + * [Feature] use_domain_sign_inbound + * [Feature] Use scores from maps if `symbols_set` is not defined + * [Fix] Add resolving version of radix map helper + * [Fix] Check URL before adding implicit prefix + * [Fix] Do not check pid/state when using PRNG + * [Fix] Fix CentOS logrotate script for systemd + * [Fix] Fix slash + dot in urls + * [Fix] Fix systemd version of the logrotate script + * [Fix] Propagate key when import implicit array from Lua + * [Fix] Strip spaces from map keys and values + * [Fix] Try to fix a specific case when processing milter protocol + * [Fix] Try to fix crash when a tcp connection cannot be set + * [Fix] Typo use_domain_local --> use_domain_sign_local + * [Fix] Various fixes to once_received module + * [Project] Store hits counters for map elements 1.7.3: 10 Apr 2018 - * [CritFix] Plug bad memory leak in protocol reply - * [Feature] Add avx2 codec for base64 - * [Feature] Add method to receive all URL flags from Lua API - * [Feature] Allow to fold headers on stop characters - * [Feature] Allow to set lua_cpath from options - * [Feature] Allow to specify custom rejection message in milter - * [Feature] Deal with unnormalised Unicode obfuscation - * [Feature] Do not detect language twice for relative parts - * [Feature] Implement oversigning feature - * [Feature] Implement silent logging level to minimize noise in logs - * [Feature] Improve URL_IN_SUBJECT rule - * [Feature] Use hashing to reduce redis attack surface - * [Fix] Add oversigning for the most important headers - * [Fix] add 'rewrite subject' to History dropdown - * [Fix] Another fix in folding algorithm - * [Fix] Do not call multimap addr for parts of addr if filter is presented - * [Fix] Do not clean hostname on generic reset - * [Fix] Do not create pid file in no-fork mode - * [Fix] Fix fold_after case to preserve multiple spaces - * [Fix] Fix folding and folding tests - * [Fix] Fix hostname usage in milter mode - * [Fix] Fix lua RSA verify and its tests - * [Fix] Fix metadata exporter send_mail backend (#2124) - * [Fix] Fix processing of '\v' in libucl - * [Fix] Fix shemaless URLs detection - * [Fix] Fix support of multiple headers in sign_header - * [Fix] Fix usage of util.parse_mail_address - * [Fix] Fix weights of dynamic squeezed rules - * [Fix] Leak from bucket before checking the burst - * [Fix] Stop using own localtime as DST could be messy in many cases - * [Fix] Treat unnormalised URLs as obscured - * [Rework] Restore leaky bucket model in ratelimit plugin - * [WebUI] Add messages total to throughput summary - * [WebUI] Add symbols order selector to history - * [WebUI] Config: Load list on demand - * [WebUI] Fix modalBody for maps that appear more than once - * [WebUI] History: Fix Tooltips on paging, filtering and sorting - * [WebUI] Remove a previously-attached event handler - * [WebUI] Update D3 to v5.0.0 and jQuery to v3.3.1 + * [CritFix] Plug bad memory leak in protocol reply + * [Feature] Add avx2 codec for base64 + * [Feature] Add method to receive all URL flags from Lua API + * [Feature] Allow to fold headers on stop characters + * [Feature] Allow to set lua_cpath from options + * [Feature] Allow to specify custom rejection message in milter + * [Feature] Deal with unnormalised Unicode obfuscation + * [Feature] Do not detect language twice for relative parts + * [Feature] Implement oversigning feature + * [Feature] Implement silent logging level to minimize noise in logs + * [Feature] Improve URL_IN_SUBJECT rule + * [Feature] Use hashing to reduce redis attack surface + * [Fix] Add oversigning for the most important headers + * [Fix] add 'rewrite subject' to History dropdown + * [Fix] Another fix in folding algorithm + * [Fix] Do not call multimap addr for parts of addr if filter is presented + * [Fix] Do not clean hostname on generic reset + * [Fix] Do not create pid file in no-fork mode + * [Fix] Fix fold_after case to preserve multiple spaces + * [Fix] Fix folding and folding tests + * [Fix] Fix hostname usage in milter mode + * [Fix] Fix lua RSA verify and its tests + * [Fix] Fix metadata exporter send_mail backend (#2124) + * [Fix] Fix processing of '\v' in libucl + * [Fix] Fix shemaless URLs detection + * [Fix] Fix support of multiple headers in sign_header + * [Fix] Fix usage of util.parse_mail_address + * [Fix] Fix weights of dynamic squeezed rules + * [Fix] Leak from bucket before checking the burst + * [Fix] Stop using own localtime as DST could be messy in many cases + * [Fix] Treat unnormalised URLs as obscured + * [Rework] Restore leaky bucket model in ratelimit plugin + * [WebUI] Add messages total to throughput summary + * [WebUI] Add symbols order selector to history + * [WebUI] Config: Load list on demand + * [WebUI] Fix modalBody for maps that appear more than once + * [WebUI] History: Fix Tooltips on paging, filtering and sorting + * [WebUI] Remove a previously-attached event handler + * [WebUI] Update D3 to v5.0.0 and jQuery to v3.3.1 1.7.2: 23 Mar 2018 - * [Feature] Store emails in Clickhouse - * [Feature] Support single quotes in config - * [Feature] Use templates when publishing CH schema - * [Feature] Improve Docker image - * [Fix] Add rounding when printing a lot of FP variables - * [Fix] Allow to disable certain actions by assigning null to them - * [Fix] Disable results caching - * [Fix] Fix disabling of squeezed symbols - * [Fix] Fix scan time set - * [Fix] Rework logic of actions setting - * [Fix] Try to fix various Lua stack issues - * [WebUI] Add link tag for favicon.ico - * [WebUI] Display hostname:port/path in the page title + * [Feature] Store emails in Clickhouse + * [Feature] Support single quotes in config + * [Feature] Use templates when publishing CH schema + * [Feature] Improve Docker image + * [Fix] Add rounding when printing a lot of FP variables + * [Fix] Allow to disable certain actions by assigning null to them + * [Fix] Disable results caching + * [Fix] Fix disabling of squeezed symbols + * [Fix] Fix scan time set + * [Fix] Rework logic of actions setting + * [Fix] Try to fix various Lua stack issues + * [WebUI] Add link tag for favicon.ico + * [WebUI] Display hostname:port/path in the page title 1.7.1: 20 Mar 2018 - * [CritFix] Fix lowercase comparison - * [CritFix] Timezone defines seconds WEST UTC not East - * [Feature] Add filename to log format - * [Feature] Add lua rules squeezing - * [Feature] Add related symbols analysis to rspamd_stats - * [Feature] Remove upstream `X-Spam: Yes` header by default - * [Feature] rspamd_stats: Output progress info on STDERR - * [Feature] Whitelist for emails module - * [Fix] Do not allow dependencies on self - * [Fix] Do not cache metric result - * [Fix] Do not trust all issuers as a client certificate - * [Fix] Fix dependencies in lua squeeze - * [Fix] Fix enabling/disabling squeezed rules - * [Fix] Fix enabling/disabling symbols - * [Fix] Fix external dependencies - * [Fix] Fix processing of a single compressed file - * [Fix] Fix some typos - * [Fix] Fix various modules in case of empty message - * [Fix] Handle callbacks that returns table of options - * [Fix] Improve cached action interaction - * [Fix] Make dynamic conf more NaN aware - * [Fix] Never hide actions from WebUI `configuration` tab - * [Project] Implementation of Lua rules squeezing + * [CritFix] Fix lowercase comparison + * [CritFix] Timezone defines seconds WEST UTC not East + * [Feature] Add filename to log format + * [Feature] Add lua rules squeezing + * [Feature] Add related symbols analysis to rspamd_stats + * [Feature] Remove upstream `X-Spam: Yes` header by default + * [Feature] rspamd_stats: Output progress info on STDERR + * [Feature] Whitelist for emails module + * [Fix] Do not allow dependencies on self + * [Fix] Do not cache metric result + * [Fix] Do not trust all issuers as a client certificate + * [Fix] Fix dependencies in lua squeeze + * [Fix] Fix enabling/disabling squeezed rules + * [Fix] Fix enabling/disabling symbols + * [Fix] Fix external dependencies + * [Fix] Fix processing of a single compressed file + * [Fix] Fix some typos + * [Fix] Fix various modules in case of empty message + * [Fix] Handle callbacks that returns table of options + * [Fix] Improve cached action interaction + * [Fix] Make dynamic conf more NaN aware + * [Fix] Never hide actions from WebUI `configuration` tab + * [Project] Implementation of Lua rules squeezing 1.7.0: 12 Mar 2018 - * [Conf] Add bayes_expiry as explicit module - * [Conf] Adjust names and weights for neural network plugin - * [Conf] Change updates url - * [Conf] Default statistics is stored in Redis now - * [Conf] Disable fann_redis module by default - * [Conf] Fix default elastic configuration - * [Conf] Fix double quote position - * [Conf] Massive config rework for new structure of symbols and scores - * [Conf] Rename Rambler BLs as they are now Rspamd's ones - * [Conf] Use dedicated rspamd.com subdomains - * [Conf] Use more data from rspamd.com fuzzy storage - * [CritFix] Add sanity guards for badly broken HTML - * [CritFix] Another errors path handling fix - * [CritFix] Another portion of tokenization fixes - * [CritFix] Do not send reject messages after set reply - * [CritFix] Fix ARC chain verification - * [CritFix] Fix crash in milter errors handler - * [CritFix] Fix memory leak in spf caching logic - * [CritFix] Fix milter commands pipelining - * [CritFix] Fix newlines detection - * [CritFix] Fix semicolons parsing in the content type - * [CritFix] Plug memory leak in zstd protocol compression - * [Feature] Add ability to match score in force_actions module - * [Feature] Add aes-rng PRF to libottery - * [Feature] Add 'composites' debug module - * [Feature] Add concept of experimental modules - * [Feature] Add DKIM trace symbol - * [Feature] Add EBL to the default config - * [Feature] Add expected ip check for emails plugin - * [Feature] Add framework to manage Redis scripts - * [Feature] Add framing for the new reputation generic plugin - * [Feature] Add function to show plugins stat - * [Feature] Add gzip compression support for clickhouse module - * [Feature] Add gzip compression support for rspamd controller - * [Feature] Add gzip support when sending lua http requests - * [Feature] Add json output for rspamd_stats - * [Feature] Add method to do a synchronous Redis connection - * [Feature] Add method to get all content-type attributes in Lua - * [Feature] Add `-m` flag to configdump to show modules states - * [Feature] Add mime types to extensions map - * [Feature] Add more features to rescore utility - * [Feature] Add more gtube like patterns to test other spam actions - * [Feature] Add more metafunctions, improve logging - * [Feature] Add more text attributes - * [Feature] Add new configwizard command to rspamadm - * [Feature] Add new tooling for stats conversation - * [Feature] Add old groups migration tool - * [Feature] Add plugins state variable - * [Feature] Add preliminary ecdsa keys support in DKIM - * [Feature] Add preliminary support of idempotent symbols - * [Feature] Add Redis server wizard - * [Feature] Add routine to convert old style stats to a new one - * [Feature] Add some sanity checks for actions and controller - * [Feature] Add statistic convertation module to configwizard - * [Feature] Add sugestions logic to mempool allocator - * [Feature] Add support of config transform in Lua - * [Feature] Add timeout to rspamc when doing corpus test - * [Feature] Add tooling to convert bayes schemas - * [Feature] Add torch conditional to configuration - * [Feature] Add torch-decisiontree package - * [Feature] Add torch-optim contrib package - * [Feature] Add TTL autodetection - * [Feature] Add urls reputation to the reputation framework - * [Feature] Allow floating and negative values in expressions limits - * [Feature] Allow multiple CTs in full extensions map - * [Feature] Allow multiple fann rules - * [Feature] Allow randomly select User-Agent from a list - * [Feature] Allow rspamadm commands to export methods in Lua - * [Feature] Allow rule specific min_bytes in fuzzy check - * [Feature] Allow to adjust symbols scores from Lua - * [Feature] Allow to attach stat signature to messages - * [Feature] Allow to change SMTP from via milter headers - * [Feature] Allow to configure monitored - * [Feature] Allow to create directories in Lua API - * [Feature] Allow to disable torch and skip train samples for ANN - * [Feature] Allow to discard messages dynamically - * [Feature] Allow to enable/disable languages from the detector - * [Feature] Allow to generate DKIM keys from rspamadm API - * [Feature] Allow to get CPU flags from Lua - * [Feature] Allow to have high precision timestamps in logs - * [Feature] Allow to insert headers into specific position - * [Feature] Allow to limit redirector requests per task - * [Feature] Allow to load and use dynamic ANNs with torch - * [Feature] Allow to quarantine rejected messages using milter interface - * [Feature] Allow to receive signing keys from mempool vars - * [Feature] Allow to reserve elements in libucl - * [Feature] Allow to reuse signal handlers chains - * [Feature] Allow to set custom mempool variables from settings - * [Feature] Allow to set headers from settings - * [Feature] Allow to set Settings-Id for all connections - * [Feature] Allow to skip real action and add a header instead - * [Feature] Allow to skip specific hashes in fuzzy storage - * [Feature] Allow to spawn asynchronous processes from Lua - * [Feature] Allow to specify number of threads for ANN learning - * [Feature] Allow to use global lua maps in settings - * [Feature] Allow to use postfilters in composites - * [Feature] Allow to verify signatures from HTTP headers in maps - * [Feature] Antivirus: ordered pattern matches - * [Feature] Authentication-Results: support hiding usernames - * [Feature] Automatically create tables in clickhouse - * [Feature] Catch next-to-last bad extension - * [Feature] Check cached maps more frequently - * [Feature] Check groups sanity - * [Feature] Deal with obscured URLs with @ symbols - * [Feature] Enhance task:store_in_file method - * [Feature] Export password encryption routines to Redis - * [Feature] Filter nan and inf when adding scores - * [Feature] Finalize 7zip files support - * [Feature] Further improvements in language detection - * [Feature] Further improvements in language detection algorithm - * [Feature] Generic key name expansion for Redis keys - * [Feature] Hash whitelist for fuzzy_check - * [Feature] Implement bayes signatures storage - * [Feature] Implement buckets for Redis backend - * [Feature] Implement DKIM reputation adjustments - * [Feature] Implement forked workers children monitoring - * [Feature] Implement headers flags in mime parser - * [Feature] Implement l1/l2 regularization against the current weights - * [Feature] Implement manual ANN train mode - * [Feature] Implement per-user ANN support - * [Feature] Implement torch based ANN learning - * [Feature] Implement upstreams logic for clickhouse exporter - * [Feature] Import torch to Rspamd... - * [Feature] Improve allocation policy when interacting with Lua - * [Feature] Improve Lua/C interaction in history_redis - * [Feature] Improve multiple fuzzy results combining - * [Feature] Improve parsing of DKIM keys: parse algorithm - * [Feature] Improve subprocesses termination handle - * [Feature] Improve symbol type parsing in Lua API - * [Feature] Metadata Exporter: e-Mail Alerts: support multiple recipients; alerting senders/recipients/users (#1600) - * [Feature] Milter headers: support adding/removing arbitrary headers from config - * [Feature] More metatokens - * [Feature] Multimap: checking of symbol options - * [Feature] Multimap: template URL filter - * [Feature] New bayes expiry plugin - * [Feature] Periodically save rspamd stats to disk - * [Feature] Preliminary import of the elasticsearch module - * [Feature] Ratelimit: allow full addresses in whitelisted_rcpts - * [Feature] Ratelimit: support fetching limits from Redis - * [Feature] RBL: received: filtering by position & flags - * [Feature] Read global maps for lua - * [Feature] Redis settings: support checking multiple keys - * [Feature] Rework fann plugin to be a normal post-filter - * [Feature] Rework logging configuration for rspamadm case - * [Feature] Rework short hashes generation to avoid FP - * [Feature] Save real ucl types when exporting to Lua - * [Feature] Set TCP_NODELAY for milter sockets - * [Feature] Setup DKIM signing from configwizard - * [Feature] Skip certain symbols from ANN classify - * [Feature] Store plugins state - * [Feature] Support etag for HTTP maps - * [Feature] Support Expires header when using HTTP maps - * [Feature] Support sending given header multiple times in lua_http - * [Feature] Support sha512 in DKIM signatures - * [Feature] Try to detect HTML messages better - * [Feature] Use array instead of queue to reduce memory fragmentation - * [Feature] Use controller port by default when connecting to local IP - * [Feature] Use rdtsc where possible - * [Fix] Actively load skip hashes map in fuzzy storage - * [Fix] Add another workaround to display history properly - * [Fix] Add definition for old glib compatibility method - * [Fix] Add missing rspamadm control options to help - * [Fix] Add workaround for IPv6 in sendmail - * [Fix] Add workaround for system with non-XSI compatible tzset - * [Fix] Allow oversigning in DKIM signatures - * [Fix] Allow to check negative scores in force_actions - * [Fix] Allow to have negative actions limits - * [Fix] Allow to set any layers number for fann rules - * [Fix] Another fix for rdtcs - * [Fix] Another fix to lua xmlrpc - * [Fix] Another try to deal with #1998 - * [Fix] Another try to fix #1998 - * [Fix] Another try to fix threading in torch - * [Fix] Apply language detection when adding fuzzy hashes - * [Fix] ARC: Fix Lua 5.3 compatibility; timestamp should be integer - * [Fix] Authentication Results: Fix SPF smtp.mail_from - * [Fix] Auth-Results: Multiple DKIM signatures - * [Fix] Avoid changing content-transfer-encoding header's value - * [Fix] Better handling of the legacy protocol - * [Fix] Check decoded headers sanity (e.g. by excluding \0) - * [Fix] Check for magic when checking for an archive - * [Fix] Cleanup mess with groups - * [Fix] Clickhouse: Insertion in the symbols table - * [Fix] Crash in URL processing - * [Fix] Deal with another case when processing exceptions - * [Fix] Deal with deeply nested messages more aggressively - * [Fix] Deal with nan and inf encoding in json/ucl - * [Fix] Deal with non-key arguments in lua_redis.exec_script - * [Fix] Deal with unknown weight - * [Fix] Deal with URLs with no slashes after protocol - * [Fix] Deal with URLs wrapped in [] in text parts - * [Fix] Deal with zero scores symbols - * [Fix] Default monitoring domain for surbl plugin - * [Fix] Delay upstream re-resolving when one upstream is defined - * [Fix] Detection of maillist optimized and fixed - * [Fix] DKIM signing: allow for auth_only to be false - * [Fix] DMARC: require report_settings for sending reports only - * [Fix] Do not allow garbadge when checking url domain - * [Fix] Do not cache SPF records with PTR elements - * [Fix] Do not constantly re-resolve failed upstreams with a single element - * [Fix] Do not crash if no words defined - * [Fix] Do not crash on empty subtype - * [Fix] Do not expose spamtrap messages to SMTP reply - * [Fix] Do not fail rbl plugin when there are no received or emails - * [Fix] Do not ignore short words - * [Fix] Do not include idempotent/nostat symbols to checksum - * [Fix] Do not override groups when converting metrics - * [Fix] Do not override unix socket group when group comes before owner - * [Fix] Do not skip the last character - * [Fix] Do not spawn too many workers by default - * [Fix] Do not stop monitored on dns errors - * [Fix] Do not stop parsing headers on bad IP header - * [Fix] Do not strip last character in the last word - * [Fix] Do not treat script content as text - * [Fix] Do not try to connect to non-supported addresses - * [Fix] Do not try to dereference last character - * [Fix] Do not try to sign unknown domains - * [Fix] Don't use whitelist/greylist maps as regexp, but as map - * [Fix] Erase unknown HTML entities - * [Fix] Exim Received header protocol parsing - * [Fix] First load selector_map and path_map. And only return false when domain not found if try_fallback is false - * [Fix] Fix a lot of FP in chartable in mixed languages - * [Fix] Fix ANN checks - * [Fix] Fix ANN loading logic - * [Fix] Fix another tokenization issue - * [Fix] Fix autolearn parameters reading - * [Fix] Fix bad archive characters stripping - * [Fix] Fix bad extension check - * [Fix] Fix bayes schema conversion - * [Fix] Fix blacklists and DMARC in whitelist - * [Fix] Fix brain-damaged torch build system - * [Fix] Fix build on FreeBSD - * [Fix] Fix clickhouse exporter - * [Fix] Fix clickhouse schema - * [Fix] Fix comparision - * [Fix] Fix composites processing - * [Fix] Fix connecting to a unix socket in rspamadm statconvert - * [Fix] Fix couple of warnings - * [Fix] Fix crashes in the rspamd_control path - * [Fix] Fix deletion from hash - * [Fix] Fix DKIM forgeries via multiple headers - * [Fix] FIx dynamic conf plugin - * [Fix] Fix emails detection - * [Fix] Fix empty headers simple canonicalization - * [Fix] Fix empty threshold check in greylisting module - * [Fix] Fix encrypted legacy reply in fuzzy storage - * [Fix] Fix enormous scores for R_WHITE_ON_WHITE - * [Fix] Fix exceptions list in surbl - * [Fix] Fix *_EXCESS_BASE64 rules - * [Fix] Fix expire rounding - * [Fix] Fix extra hits in PCRE mode for regular expressions - * [Fix] Fix format strings - * [Fix] Fix get_content method - * [Fix] Fix groups override when defining symbols - * [Fix] Fix learned count in new schema - * [Fix] Fix learn errors propagation - * [Fix] Fix loading of per-user redis backend for statistics - * [Fix] Fix logging buffer corruption in case of repeated messages - * [Fix] Fix lua cached elements invalidation - * [Fix] Fix merging of the implicit arrays - * [Fix] Fix mime_types scoring - * [Fix] Fix multiple headers in DKIM headers list - * [Fix] Fix null callee case in clang plugin - * [Fix] Fix obscured url in format user@@example.com - * [Fix] Fix parsing of the per-user script - * [Fix] Fix priorities in rspamd_update, disable rules execution - * [Fix] Fix processing of closed tags - * [Fix] Fix processing of idempotent rules when autolearn fails - * [Fix] Fix processing of multipart parts with no headers - * [Fix] Fix processing of skip-hashes in fuzzy storage - * [Fix] Fix PTR processing in SPF - * [Fix] Fix pushing country to clickhouse asn table - * [Fix] Fix random forests module - * [Fix] Fix real IP parsing for some strange Exim received - * [Fix] Fix Redis timeout setup - * [Fix] Fix reload crash when hyperscan is enabled - * [Fix] Fix reusing of redis connection after exec - * [Fix] Fix sanity checks on macro value - * [Fix] Fix setting of path and cpath for Lua - * [Fix] Fix setting of signals when spawning a thread - * [Fix] Fix text splitting: stack overflow (too many captures) - * [Fix] Fix ticks processing - * [Fix] Fix upstream addrs updating - * [Fix] Fix urls/emails distinguishing found in queries - * [Fix] Fix user settings check - * [Fix] Fix variable increment - * [Fix] Fix various issues in stat_convert - * [Fix] F-PROT Antivirus infection string for all known occurences - * [Fix] F-PROT Antivirus: only check return code to determine infection - * [Fix] Further fixes around floating point expressions - * [Fix] Further fixes to ANN module - * [Fix] Further fixes to rescore tool - * [Fix] Further fixes to support ES 6 - * [Fix] Further tokenization fixes - * [Fix] Greylisting set phase is not idempotent - * [Fix] Handle proxy copy errors - * [Fix] Header checks: Fix get_raw_header method - * [Fix] Header checks: REPLYTO_UNPARSEABLE rule - * [Fix] Kill spawned processes on termination - * [Fix] Load skip map from all processes as shared cache is unavailable - * [Fix] Lowercase HTTP headers to make them searchable from Lua - * [Fix] Lowercase words - * [Fix] Lua_http: freeing - * [Fix] Lua: lpeg to be loaded with rspamd_lua_add_preload, to avoid "rspamd_config_read: rcl parse error: cannot init lua file […] module 'lpeg' not found" - * [Fix] Map absence is not an error - * [Fix] Metadata exporter: check IP sanity - * [Fix] Milter headers: custom headers: removing headers - * [Fix] Milter headers: skip_local / skip_authenticated settings - * [Fix] Milter headers: X-Spamd-Result header if X-Virus ran first - * [Fix] mime_types: fix next-to-last extension length check - * [Fix] More hacks to deal with old configs - * [Fix] Move composites second pass to the dedicated stage - * [Fix] Multimap: received: filtering of artificial header - * [Fix] Multiple fixes in torch based ANN plugins - * [Fix] Once more (#1879) fix bad extension check - * [Fix] Optimize rspamd_fstring_t reallocations - * [Fix] options.local_networks setting - * [Fix] Parse HREF urls without explicit prefix - * [Fix] Plan new event on HTTP errors - * [Fix] Plug another possible memory leak - * [Fix] Plug memory leak - * [Fix] Plug memory leak in lua_tcp - * [Fix] Plug memory leak when setting email addresses from Lua - * [Fix] Propagate learn/stat errors more precisely - * [Fix] Ratelimit: fix whitelisted_rcpts matching - * [Fix] Ratelimit: lowercase email addresses - * [Fix] RBL: received: deal with missing data (#1965) - * [Fix] Rebalance and slightly rework MX check plugin - * [Fix] Redis key expansion: EVAL: deal with strings - * [Fix] Redis script loading in DMARC; URL tags; URL reputation - * [Fix] Reject invalid bh for DKIM signatures earlier - * [Fix] Relax pem signature detection - * [Fix] Relax unicode properties requirements for chartable module - * [Fix] Remove extra noise from dkim and arc signing - * [Fix] Remove hop-by-hop headers in proxy - * [Fix] Remove incorrect method `task:set_metric_subject` - * [Fix] Replace space like characters in headers with plain space - * [Fix] Restore old style ratelimits support - * [Fix] Rework elasticsearch plugin - * [Fix] Rewriting subjects via force actions module - * [Fix] RPM postinstall - * [Fix] Sanitize IP in history redis - * [Fix] Select the correct signature when doing simple canon - * [Fix] Set CLOEXEC flag on files opened - * [Fix] Setting check_local / check_authed in plugins (#1954) - * [Fix] Settings: avoid checking invalid IP (#1981) - * [Fix] Settings: header: deal with multiple settings (#1988) - * [Fix] Skip checks if both extensions are not bad - * [Fix] Skip nostat tokens when get number of tokens - * [Fix] Some more fixes towards emails detection - * [Fix] SpamAssassin: Fail check_freemail_header if regexp didn't match - * [Fix] Stop using of g_slice... - * [Fix] Switch rspamadm logging to message level - * [Fix] Symbol 'FANNR_SPAM' has its score defined.. - * [Fix] Table parameter for rspamd_config:add_doc() - * [Fix] Treat 'rewrite subject' as spam action - * [Fix] Try harder in passing IPv6 addresses - * [Fix] Try harder to find rfc822 notifications - * [Fix] Try harder to find urls - * [Fix] Use decoded values when parsing mime addresses - * [Fix] Use full URL when making an HTTP request - * [Fix] Use greylisting threshold in greylisting module - * [Fix] Use n_words attribute from ngramms - * [Fix] Use raw urls when sending requests to redirector - * [Fix] Use the right boolean operator on error check - * [Fix] Use weight from map for fuzzy scoring - * [Fix] Various fixes to elastic plugin - * [Fix] Various fixes to fann_redis instantiation - * [Fix] Various improvements in language detection - * [Fix] Virus infection string for F-PROT Antivirus - * [Fix] Virus infetction string for F-PROT Antivirus - * [Fix] WebUI: use relative path for savemap (#1943) - * [Fix] WHITE_ON_WHITE: Ensure score is matched to part that fired the rule - * [Fix] Write configuration changes as UCL config - * [Project] Add detection logic for words - * [Project] Add fast debug logging infrastructure - * [Project] Add more flags to languages - * [Project] Add n-gramms data files - * [Project] Add ngramms frequencies detector - * [Project] Add random words selection logic - * [Project] Add unigramms to language detection as well - * [Project] Convert all C modules to fast debug infrastructure - * [Project] Detect some languages based on unicode script - * [Project] Enable fast debug lookup for some modules - * [Project] Enable language detector init in scanner workers - * [Project] Further improvements to language detector - * [Project] Implement logic of ngramms application - * [Project] Improve weighting in lang_detection - * [Project] Initialize language detector - * [Project] Preliminary version of ngramms based language detector - * [Project] Preliminary version of the new stat_convert - * [Project] Remove old language detector - * [Project] Rework language detection ngramms structure - * [Project] Start language detection project - * [Project] Start rework of language detection to improve quality - * [Project] Use fast debug logging check - * [Rework] Add frame for new reputation based IP score module - * [Rework] Continue stat_convert rework task - * [Rework] Implement new version of fuzzy replies - * [Rework] Improve readability of xmlrpc API - * [Rework] Kill metrics!11 - * [Rework] Ratelimit module - * [Rework] Rename fann_redis to neural plugin - * [Rework] Reorganize mime_types module - * [Rework] Rework rescore utility - * [Rework] Rewrite model and learning logic for rescore - * [Rework] Run post-loads when all initialization is completed - * [Rework] Simplify lua path initialization - * [Rework] Start major stat_convert rework - * [Rework] Start mempool fragmentation reduce project - * [Rework] Start moving of fann redis to torch - * [Rework] Stop embedding rspamadm scripts into C - * [Rework] Use floating point arithmetics in Rspamd expressions - * [Rework] Use frequencies distribution in language detector - * [Rules] Penalise R_BAD_CTE_7BIT for utf8 messages - * [WebUI] Compact graph selectors - * [WebUI] Escape strings inside HTML in history - * [WebUI] Fix message count in throughput summary (#1724) - * [WebUI] Fix NaNs display on Throughput graph - * [WebUI] Migrate widgets to D3 v4 - * [WebUI] Restore passwordless login support (#2003) - * [WebUI] Show symbol descriptions as tooltips in history - * [WebUI] Stop using commas in pie chart tooltips - * [WebUI] Update D3 and jQuery - * [WebUI] Update D3Evolution 1.0.0 -> 1.1.0 + * [Conf] Add bayes_expiry as explicit module + * [Conf] Adjust names and weights for neural network plugin + * [Conf] Change updates url + * [Conf] Default statistics is stored in Redis now + * [Conf] Disable fann_redis module by default + * [Conf] Fix default elastic configuration + * [Conf] Fix double quote position + * [Conf] Massive config rework for new structure of symbols and scores + * [Conf] Rename Rambler BLs as they are now Rspamd's ones + * [Conf] Use dedicated rspamd.com subdomains + * [Conf] Use more data from rspamd.com fuzzy storage + * [CritFix] Add sanity guards for badly broken HTML + * [CritFix] Another errors path handling fix + * [CritFix] Another portion of tokenization fixes + * [CritFix] Do not send reject messages after set reply + * [CritFix] Fix ARC chain verification + * [CritFix] Fix crash in milter errors handler + * [CritFix] Fix memory leak in spf caching logic + * [CritFix] Fix milter commands pipelining + * [CritFix] Fix newlines detection + * [CritFix] Fix semicolons parsing in the content type + * [CritFix] Plug memory leak in zstd protocol compression + * [Feature] Add ability to match score in force_actions module + * [Feature] Add aes-rng PRF to libottery + * [Feature] Add 'composites' debug module + * [Feature] Add concept of experimental modules + * [Feature] Add DKIM trace symbol + * [Feature] Add EBL to the default config + * [Feature] Add expected ip check for emails plugin + * [Feature] Add framework to manage Redis scripts + * [Feature] Add framing for the new reputation generic plugin + * [Feature] Add function to show plugins stat + * [Feature] Add gzip compression support for clickhouse module + * [Feature] Add gzip compression support for rspamd controller + * [Feature] Add gzip support when sending lua http requests + * [Feature] Add json output for rspamd_stats + * [Feature] Add method to do a synchronous Redis connection + * [Feature] Add method to get all content-type attributes in Lua + * [Feature] Add `-m` flag to configdump to show modules states + * [Feature] Add mime types to extensions map + * [Feature] Add more features to rescore utility + * [Feature] Add more gtube like patterns to test other spam actions + * [Feature] Add more metafunctions, improve logging + * [Feature] Add more text attributes + * [Feature] Add new configwizard command to rspamadm + * [Feature] Add new tooling for stats conversation + * [Feature] Add old groups migration tool + * [Feature] Add plugins state variable + * [Feature] Add preliminary ecdsa keys support in DKIM + * [Feature] Add preliminary support of idempotent symbols + * [Feature] Add Redis server wizard + * [Feature] Add routine to convert old style stats to a new one + * [Feature] Add some sanity checks for actions and controller + * [Feature] Add statistic convertation module to configwizard + * [Feature] Add sugestions logic to mempool allocator + * [Feature] Add support of config transform in Lua + * [Feature] Add timeout to rspamc when doing corpus test + * [Feature] Add tooling to convert bayes schemas + * [Feature] Add torch conditional to configuration + * [Feature] Add torch-decisiontree package + * [Feature] Add torch-optim contrib package + * [Feature] Add TTL autodetection + * [Feature] Add urls reputation to the reputation framework + * [Feature] Allow floating and negative values in expressions limits + * [Feature] Allow multiple CTs in full extensions map + * [Feature] Allow multiple fann rules + * [Feature] Allow randomly select User-Agent from a list + * [Feature] Allow rspamadm commands to export methods in Lua + * [Feature] Allow rule specific min_bytes in fuzzy check + * [Feature] Allow to adjust symbols scores from Lua + * [Feature] Allow to attach stat signature to messages + * [Feature] Allow to change SMTP from via milter headers + * [Feature] Allow to configure monitored + * [Feature] Allow to create directories in Lua API + * [Feature] Allow to disable torch and skip train samples for ANN + * [Feature] Allow to discard messages dynamically + * [Feature] Allow to enable/disable languages from the detector + * [Feature] Allow to generate DKIM keys from rspamadm API + * [Feature] Allow to get CPU flags from Lua + * [Feature] Allow to have high precision timestamps in logs + * [Feature] Allow to insert headers into specific position + * [Feature] Allow to limit redirector requests per task + * [Feature] Allow to load and use dynamic ANNs with torch + * [Feature] Allow to quarantine rejected messages using milter interface + * [Feature] Allow to receive signing keys from mempool vars + * [Feature] Allow to reserve elements in libucl + * [Feature] Allow to reuse signal handlers chains + * [Feature] Allow to set custom mempool variables from settings + * [Feature] Allow to set headers from settings + * [Feature] Allow to set Settings-Id for all connections + * [Feature] Allow to skip real action and add a header instead + * [Feature] Allow to skip specific hashes in fuzzy storage + * [Feature] Allow to spawn asynchronous processes from Lua + * [Feature] Allow to specify number of threads for ANN learning + * [Feature] Allow to use global lua maps in settings + * [Feature] Allow to use postfilters in composites + * [Feature] Allow to verify signatures from HTTP headers in maps + * [Feature] Antivirus: ordered pattern matches + * [Feature] Authentication-Results: support hiding usernames + * [Feature] Automatically create tables in clickhouse + * [Feature] Catch next-to-last bad extension + * [Feature] Check cached maps more frequently + * [Feature] Check groups sanity + * [Feature] Deal with obscured URLs with @ symbols + * [Feature] Enhance task:store_in_file method + * [Feature] Export password encryption routines to Redis + * [Feature] Filter nan and inf when adding scores + * [Feature] Finalize 7zip files support + * [Feature] Further improvements in language detection + * [Feature] Further improvements in language detection algorithm + * [Feature] Generic key name expansion for Redis keys + * [Feature] Hash whitelist for fuzzy_check + * [Feature] Implement bayes signatures storage + * [Feature] Implement buckets for Redis backend + * [Feature] Implement DKIM reputation adjustments + * [Feature] Implement forked workers children monitoring + * [Feature] Implement headers flags in mime parser + * [Feature] Implement l1/l2 regularization against the current weights + * [Feature] Implement manual ANN train mode + * [Feature] Implement per-user ANN support + * [Feature] Implement torch based ANN learning + * [Feature] Implement upstreams logic for clickhouse exporter + * [Feature] Import torch to Rspamd... + * [Feature] Improve allocation policy when interacting with Lua + * [Feature] Improve Lua/C interaction in history_redis + * [Feature] Improve multiple fuzzy results combining + * [Feature] Improve parsing of DKIM keys: parse algorithm + * [Feature] Improve subprocesses termination handle + * [Feature] Improve symbol type parsing in Lua API + * [Feature] Metadata Exporter: e-Mail Alerts: support multiple recipients; alerting senders/recipients/users (#1600) + * [Feature] Milter headers: support adding/removing arbitrary headers from config + * [Feature] More metatokens + * [Feature] Multimap: checking of symbol options + * [Feature] Multimap: template URL filter + * [Feature] New bayes expiry plugin + * [Feature] Periodically save rspamd stats to disk + * [Feature] Preliminary import of the elasticsearch module + * [Feature] Ratelimit: allow full addresses in whitelisted_rcpts + * [Feature] Ratelimit: support fetching limits from Redis + * [Feature] RBL: received: filtering by position & flags + * [Feature] Read global maps for lua + * [Feature] Redis settings: support checking multiple keys + * [Feature] Rework fann plugin to be a normal post-filter + * [Feature] Rework logging configuration for rspamadm case + * [Feature] Rework short hashes generation to avoid FP + * [Feature] Save real ucl types when exporting to Lua + * [Feature] Set TCP_NODELAY for milter sockets + * [Feature] Setup DKIM signing from configwizard + * [Feature] Skip certain symbols from ANN classify + * [Feature] Store plugins state + * [Feature] Support etag for HTTP maps + * [Feature] Support Expires header when using HTTP maps + * [Feature] Support sending given header multiple times in lua_http + * [Feature] Support sha512 in DKIM signatures + * [Feature] Try to detect HTML messages better + * [Feature] Use array instead of queue to reduce memory fragmentation + * [Feature] Use controller port by default when connecting to local IP + * [Feature] Use rdtsc where possible + * [Fix] Actively load skip hashes map in fuzzy storage + * [Fix] Add another workaround to display history properly + * [Fix] Add definition for old glib compatibility method + * [Fix] Add missing rspamadm control options to help + * [Fix] Add workaround for IPv6 in sendmail + * [Fix] Add workaround for system with non-XSI compatible tzset + * [Fix] Allow oversigning in DKIM signatures + * [Fix] Allow to check negative scores in force_actions + * [Fix] Allow to have negative actions limits + * [Fix] Allow to set any layers number for fann rules + * [Fix] Another fix for rdtcs + * [Fix] Another fix to lua xmlrpc + * [Fix] Another try to deal with #1998 + * [Fix] Another try to fix #1998 + * [Fix] Another try to fix threading in torch + * [Fix] Apply language detection when adding fuzzy hashes + * [Fix] ARC: Fix Lua 5.3 compatibility; timestamp should be integer + * [Fix] Authentication Results: Fix SPF smtp.mail_from + * [Fix] Auth-Results: Multiple DKIM signatures + * [Fix] Avoid changing content-transfer-encoding header's value + * [Fix] Better handling of the legacy protocol + * [Fix] Check decoded headers sanity (e.g. by excluding \0) + * [Fix] Check for magic when checking for an archive + * [Fix] Cleanup mess with groups + * [Fix] Clickhouse: Insertion in the symbols table + * [Fix] Crash in URL processing + * [Fix] Deal with another case when processing exceptions + * [Fix] Deal with deeply nested messages more aggressively + * [Fix] Deal with nan and inf encoding in json/ucl + * [Fix] Deal with non-key arguments in lua_redis.exec_script + * [Fix] Deal with unknown weight + * [Fix] Deal with URLs with no slashes after protocol + * [Fix] Deal with URLs wrapped in [] in text parts + * [Fix] Deal with zero scores symbols + * [Fix] Default monitoring domain for surbl plugin + * [Fix] Delay upstream re-resolving when one upstream is defined + * [Fix] Detection of maillist optimized and fixed + * [Fix] DKIM signing: allow for auth_only to be false + * [Fix] DMARC: require report_settings for sending reports only + * [Fix] Do not allow garbadge when checking url domain + * [Fix] Do not cache SPF records with PTR elements + * [Fix] Do not constantly re-resolve failed upstreams with a single element + * [Fix] Do not crash if no words defined + * [Fix] Do not crash on empty subtype + * [Fix] Do not expose spamtrap messages to SMTP reply + * [Fix] Do not fail rbl plugin when there are no received or emails + * [Fix] Do not ignore short words + * [Fix] Do not include idempotent/nostat symbols to checksum + * [Fix] Do not override groups when converting metrics + * [Fix] Do not override unix socket group when group comes before owner + * [Fix] Do not skip the last character + * [Fix] Do not spawn too many workers by default + * [Fix] Do not stop monitored on dns errors + * [Fix] Do not stop parsing headers on bad IP header + * [Fix] Do not strip last character in the last word + * [Fix] Do not treat script content as text + * [Fix] Do not try to connect to non-supported addresses + * [Fix] Do not try to dereference last character + * [Fix] Do not try to sign unknown domains + * [Fix] Don't use whitelist/greylist maps as regexp, but as map + * [Fix] Erase unknown HTML entities + * [Fix] Exim Received header protocol parsing + * [Fix] First load selector_map and path_map. And only return false when domain not found if try_fallback is false + * [Fix] Fix a lot of FP in chartable in mixed languages + * [Fix] Fix ANN checks + * [Fix] Fix ANN loading logic + * [Fix] Fix another tokenization issue + * [Fix] Fix autolearn parameters reading + * [Fix] Fix bad archive characters stripping + * [Fix] Fix bad extension check + * [Fix] Fix bayes schema conversion + * [Fix] Fix blacklists and DMARC in whitelist + * [Fix] Fix brain-damaged torch build system + * [Fix] Fix build on FreeBSD + * [Fix] Fix clickhouse exporter + * [Fix] Fix clickhouse schema + * [Fix] Fix comparision + * [Fix] Fix composites processing + * [Fix] Fix connecting to a unix socket in rspamadm statconvert + * [Fix] Fix couple of warnings + * [Fix] Fix crashes in the rspamd_control path + * [Fix] Fix deletion from hash + * [Fix] Fix DKIM forgeries via multiple headers + * [Fix] FIx dynamic conf plugin + * [Fix] Fix emails detection + * [Fix] Fix empty headers simple canonicalization + * [Fix] Fix empty threshold check in greylisting module + * [Fix] Fix encrypted legacy reply in fuzzy storage + * [Fix] Fix enormous scores for R_WHITE_ON_WHITE + * [Fix] Fix exceptions list in surbl + * [Fix] Fix *_EXCESS_BASE64 rules + * [Fix] Fix expire rounding + * [Fix] Fix extra hits in PCRE mode for regular expressions + * [Fix] Fix format strings + * [Fix] Fix get_content method + * [Fix] Fix groups override when defining symbols + * [Fix] Fix learned count in new schema + * [Fix] Fix learn errors propagation + * [Fix] Fix loading of per-user redis backend for statistics + * [Fix] Fix logging buffer corruption in case of repeated messages + * [Fix] Fix lua cached elements invalidation + * [Fix] Fix merging of the implicit arrays + * [Fix] Fix mime_types scoring + * [Fix] Fix multiple headers in DKIM headers list + * [Fix] Fix null callee case in clang plugin + * [Fix] Fix obscured url in format user@@example.com + * [Fix] Fix parsing of the per-user script + * [Fix] Fix priorities in rspamd_update, disable rules execution + * [Fix] Fix processing of closed tags + * [Fix] Fix processing of idempotent rules when autolearn fails + * [Fix] Fix processing of multipart parts with no headers + * [Fix] Fix processing of skip-hashes in fuzzy storage + * [Fix] Fix PTR processing in SPF + * [Fix] Fix pushing country to clickhouse asn table + * [Fix] Fix random forests module + * [Fix] Fix real IP parsing for some strange Exim received + * [Fix] Fix Redis timeout setup + * [Fix] Fix reload crash when hyperscan is enabled + * [Fix] Fix reusing of redis connection after exec + * [Fix] Fix sanity checks on macro value + * [Fix] Fix setting of path and cpath for Lua + * [Fix] Fix setting of signals when spawning a thread + * [Fix] Fix text splitting: stack overflow (too many captures) + * [Fix] Fix ticks processing + * [Fix] Fix upstream addrs updating + * [Fix] Fix urls/emails distinguishing found in queries + * [Fix] Fix user settings check + * [Fix] Fix variable increment + * [Fix] Fix various issues in stat_convert + * [Fix] F-PROT Antivirus infection string for all known occurences + * [Fix] F-PROT Antivirus: only check return code to determine infection + * [Fix] Further fixes around floating point expressions + * [Fix] Further fixes to ANN module + * [Fix] Further fixes to rescore tool + * [Fix] Further fixes to support ES 6 + * [Fix] Further tokenization fixes + * [Fix] Greylisting set phase is not idempotent + * [Fix] Handle proxy copy errors + * [Fix] Header checks: Fix get_raw_header method + * [Fix] Header checks: REPLYTO_UNPARSEABLE rule + * [Fix] Kill spawned processes on termination + * [Fix] Load skip map from all processes as shared cache is unavailable + * [Fix] Lowercase HTTP headers to make them searchable from Lua + * [Fix] Lowercase words + * [Fix] Lua_http: freeing + * [Fix] Lua: lpeg to be loaded with rspamd_lua_add_preload, to avoid "rspamd_config_read: rcl parse error: cannot init lua file […] module 'lpeg' not found" + * [Fix] Map absence is not an error + * [Fix] Metadata exporter: check IP sanity + * [Fix] Milter headers: custom headers: removing headers + * [Fix] Milter headers: skip_local / skip_authenticated settings + * [Fix] Milter headers: X-Spamd-Result header if X-Virus ran first + * [Fix] mime_types: fix next-to-last extension length check + * [Fix] More hacks to deal with old configs + * [Fix] Move composites second pass to the dedicated stage + * [Fix] Multimap: received: filtering of artificial header + * [Fix] Multiple fixes in torch based ANN plugins + * [Fix] Once more (#1879) fix bad extension check + * [Fix] Optimize rspamd_fstring_t reallocations + * [Fix] options.local_networks setting + * [Fix] Parse HREF urls without explicit prefix + * [Fix] Plan new event on HTTP errors + * [Fix] Plug another possible memory leak + * [Fix] Plug memory leak + * [Fix] Plug memory leak in lua_tcp + * [Fix] Plug memory leak when setting email addresses from Lua + * [Fix] Propagate learn/stat errors more precisely + * [Fix] Ratelimit: fix whitelisted_rcpts matching + * [Fix] Ratelimit: lowercase email addresses + * [Fix] RBL: received: deal with missing data (#1965) + * [Fix] Rebalance and slightly rework MX check plugin + * [Fix] Redis key expansion: EVAL: deal with strings + * [Fix] Redis script loading in DMARC; URL tags; URL reputation + * [Fix] Reject invalid bh for DKIM signatures earlier + * [Fix] Relax pem signature detection + * [Fix] Relax unicode properties requirements for chartable module + * [Fix] Remove extra noise from dkim and arc signing + * [Fix] Remove hop-by-hop headers in proxy + * [Fix] Remove incorrect method `task:set_metric_subject` + * [Fix] Replace space like characters in headers with plain space + * [Fix] Restore old style ratelimits support + * [Fix] Rework elasticsearch plugin + * [Fix] Rewriting subjects via force actions module + * [Fix] RPM postinstall + * [Fix] Sanitize IP in history redis + * [Fix] Select the correct signature when doing simple canon + * [Fix] Set CLOEXEC flag on files opened + * [Fix] Setting check_local / check_authed in plugins (#1954) + * [Fix] Settings: avoid checking invalid IP (#1981) + * [Fix] Settings: header: deal with multiple settings (#1988) + * [Fix] Skip checks if both extensions are not bad + * [Fix] Skip nostat tokens when get number of tokens + * [Fix] Some more fixes towards emails detection + * [Fix] SpamAssassin: Fail check_freemail_header if regexp didn't match + * [Fix] Stop using of g_slice... + * [Fix] Switch rspamadm logging to message level + * [Fix] Symbol 'FANNR_SPAM' has its score defined.. + * [Fix] Table parameter for rspamd_config:add_doc() + * [Fix] Treat 'rewrite subject' as spam action + * [Fix] Try harder in passing IPv6 addresses + * [Fix] Try harder to find rfc822 notifications + * [Fix] Try harder to find urls + * [Fix] Use decoded values when parsing mime addresses + * [Fix] Use full URL when making an HTTP request + * [Fix] Use greylisting threshold in greylisting module + * [Fix] Use n_words attribute from ngramms + * [Fix] Use raw urls when sending requests to redirector + * [Fix] Use the right boolean operator on error check + * [Fix] Use weight from map for fuzzy scoring + * [Fix] Various fixes to elastic plugin + * [Fix] Various fixes to fann_redis instantiation + * [Fix] Various improvements in language detection + * [Fix] Virus infection string for F-PROT Antivirus + * [Fix] Virus infetction string for F-PROT Antivirus + * [Fix] WebUI: use relative path for savemap (#1943) + * [Fix] WHITE_ON_WHITE: Ensure score is matched to part that fired the rule + * [Fix] Write configuration changes as UCL config + * [Project] Add detection logic for words + * [Project] Add fast debug logging infrastructure + * [Project] Add more flags to languages + * [Project] Add n-gramms data files + * [Project] Add ngramms frequencies detector + * [Project] Add random words selection logic + * [Project] Add unigramms to language detection as well + * [Project] Convert all C modules to fast debug infrastructure + * [Project] Detect some languages based on unicode script + * [Project] Enable fast debug lookup for some modules + * [Project] Enable language detector init in scanner workers + * [Project] Further improvements to language detector + * [Project] Implement logic of ngramms application + * [Project] Improve weighting in lang_detection + * [Project] Initialize language detector + * [Project] Preliminary version of ngramms based language detector + * [Project] Preliminary version of the new stat_convert + * [Project] Remove old language detector + * [Project] Rework language detection ngramms structure + * [Project] Start language detection project + * [Project] Start rework of language detection to improve quality + * [Project] Use fast debug logging check + * [Rework] Add frame for new reputation based IP score module + * [Rework] Continue stat_convert rework task + * [Rework] Implement new version of fuzzy replies + * [Rework] Improve readability of xmlrpc API + * [Rework] Kill metrics!11 + * [Rework] Ratelimit module + * [Rework] Rename fann_redis to neural plugin + * [Rework] Reorganize mime_types module + * [Rework] Rework rescore utility + * [Rework] Rewrite model and learning logic for rescore + * [Rework] Run post-loads when all initialization is completed + * [Rework] Simplify lua path initialization + * [Rework] Start major stat_convert rework + * [Rework] Start mempool fragmentation reduce project + * [Rework] Start moving of fann redis to torch + * [Rework] Stop embedding rspamadm scripts into C + * [Rework] Use floating point arithmetics in Rspamd expressions + * [Rework] Use frequencies distribution in language detector + * [Rules] Penalise R_BAD_CTE_7BIT for utf8 messages + * [WebUI] Compact graph selectors + * [WebUI] Escape strings inside HTML in history + * [WebUI] Fix message count in throughput summary (#1724) + * [WebUI] Fix NaNs display on Throughput graph + * [WebUI] Migrate widgets to D3 v4 + * [WebUI] Restore passwordless login support (#2003) + * [WebUI] Show symbol descriptions as tooltips in history + * [WebUI] Stop using commas in pie chart tooltips + * [WebUI] Update D3 and jQuery + * [WebUI] Update D3Evolution 1.0.0 -> 1.1.0 1.6.6: 16 Feb 2018 - * [CritFix] Add sanity guards for badly broken HTML - * [CritFix] Another errors path handling fix - * [CritFix] Fix ARC chain verification - * [CritFix] Fix crash in milter errors handler - * [Feature] Allow to insert headers into specific position - * [Feature] Allow to receive signing keys from mempool vars - * [Feature] Authentication-Results: support hiding usernames - * [Fix] Another try to deal with #1998 - * [Fix] Another try to fix #1998 - * [Fix] Better handling of the legacy protocol - * [Fix] Check decoded headers sanity (e.g. by excluding \0) - * [Fix] Deal with nan and inf encoding in json/ucl - * [Fix] Deal with URLs wrapped in [] in text parts - * [Fix] DKIM signing: allow for auth_only to be false - * [Fix] Do not crash on empty subtype - * [Fix] Do not fail rbl plugin when there are no received or emails - * [Fix] Do not skip the last character - * [Fix] Do not try to dereference last character - * [Fix] Do not try to sign unknown domains - * [Fix] Exim Received header protocol parsing - * [Fix] First load selector_map and path_map. And only return false when domain not found if try_fallback is false - * [Fix] Fix bad archive characters stripping - * [Fix] Fix comparision - * [Fix] Fix connecting to a unix socket in rspamadm statconvert - * [Fix] Fix empty headers simple canonicalization - * [Fix] Fix extra hits in PCRE mode for regular expressions - * [Fix] Fix parsing of the per-user script - * [Fix] Fix processing of skip-hashes in fuzzy storage - * [Fix] Fix Redis timeout setup - * [Fix] Fix sanity checks on macro value - * [Fix] Fix text splitting: stack overflow (too many captures) - * [Fix] Fix urls/emails distinguishing found in queries - * [Fix] F-PROT Antivirus: only check return code to determine infection - * [Fix] Metadata exporter: check IP sanity - * [Fix] Multimap: received: filtering of artificial header - * [Fix] Plan new event on HTTP errors - * [Fix] Plug another possible memory leak - * [Fix] Remove hop-by-hop headers in proxy - * [Fix] Sanitize IP in history redis - * [Fix] Setting check_local / check_authed in plugins (#1954) - * [Fix] Settings: avoid checking invalid IP (#1981) - * [Fix] Try harder in passing IPv6 addresses - * [Fix] WebUI: use relative path for savemap (#1943) - * [WebUI] Fix message count in throughput summary (#1724) - * [WebUI] Fix NaNs display on Throughput graph - * [WebUI] Restore passwordless login support (#2003) + * [CritFix] Add sanity guards for badly broken HTML + * [CritFix] Another errors path handling fix + * [CritFix] Fix ARC chain verification + * [CritFix] Fix crash in milter errors handler + * [Feature] Allow to insert headers into specific position + * [Feature] Allow to receive signing keys from mempool vars + * [Feature] Authentication-Results: support hiding usernames + * [Fix] Another try to deal with #1998 + * [Fix] Another try to fix #1998 + * [Fix] Better handling of the legacy protocol + * [Fix] Check decoded headers sanity (e.g. by excluding \0) + * [Fix] Deal with nan and inf encoding in json/ucl + * [Fix] Deal with URLs wrapped in [] in text parts + * [Fix] DKIM signing: allow for auth_only to be false + * [Fix] Do not crash on empty subtype + * [Fix] Do not fail rbl plugin when there are no received or emails + * [Fix] Do not skip the last character + * [Fix] Do not try to dereference last character + * [Fix] Do not try to sign unknown domains + * [Fix] Exim Received header protocol parsing + * [Fix] First load selector_map and path_map. And only return false when domain not found if try_fallback is false + * [Fix] Fix bad archive characters stripping + * [Fix] Fix comparision + * [Fix] Fix connecting to a unix socket in rspamadm statconvert + * [Fix] Fix empty headers simple canonicalization + * [Fix] Fix extra hits in PCRE mode for regular expressions + * [Fix] Fix parsing of the per-user script + * [Fix] Fix processing of skip-hashes in fuzzy storage + * [Fix] Fix Redis timeout setup + * [Fix] Fix sanity checks on macro value + * [Fix] Fix text splitting: stack overflow (too many captures) + * [Fix] Fix urls/emails distinguishing found in queries + * [Fix] F-PROT Antivirus: only check return code to determine infection + * [Fix] Metadata exporter: check IP sanity + * [Fix] Multimap: received: filtering of artificial header + * [Fix] Plan new event on HTTP errors + * [Fix] Plug another possible memory leak + * [Fix] Remove hop-by-hop headers in proxy + * [Fix] Sanitize IP in history redis + * [Fix] Setting check_local / check_authed in plugins (#1954) + * [Fix] Settings: avoid checking invalid IP (#1981) + * [Fix] Try harder in passing IPv6 addresses + * [Fix] WebUI: use relative path for savemap (#1943) + * [WebUI] Fix message count in throughput summary (#1724) + * [WebUI] Fix NaNs display on Throughput graph + * [WebUI] Restore passwordless login support (#2003) 1.6.5: 22 Oct 2017 - * [CritFix] Another portion of tokenization fixes - * [CritFix] Fix memory leak in spf caching logic - * [CritFix] Fix milter commands pipelining - * [CritFix] Fix newlines detection - * [Feature] Filter nan and inf when adding scores - * [Feature] Implement headers flags in mime parser - * [Feature] Support Expires header when using HTTP maps - * [Fix] Actively load skip hashes map in fuzzy storage - * [Fix] Add workaround for IPv6 in sendmail - * [Fix] Authentication Results: Fix SPF smtp.mail_from - * [Fix] Check for magic when checking for an archive - * [Fix] Deal with another case when processing exceptions - * [Fix] Deal with URLs with no slashes after protocol - * [Fix] Do not allow garbadge when checking url domain - * [Fix] Do not ignore short words - * [Fix] Do not strip last character in the last word - * [Fix] Do not treat script content as text - * [Fix] Erase unknown HTML entities - * [Fix] Fix another tokenization issue - * [Fix] Fix DKIM forgeries via multiple headers - * [Fix] Fix emails detection - * [Fix] Fix empty threshold check in greylisting module - * [Fix] Fix enormous scores for R_WHITE_ON_WHITE - * [Fix] Fix loading of per-user redis backend for statistics - * [Fix] Fix multiple headers in DKIM headers list - * [Fix] Fix obscured url in format user@@example.com - * [Fix] Further tokenization fixes - * [Fix] Load skip map from all processes as shared cache is unavailable - * [Fix] Lowercase words - * [Fix] Milter headers: skip_local / skip_authenticated settings - * [Fix] Milter headers: X-Spamd-Result header if X-Virus ran first - * [Fix] Ratelimit: fix whitelisted_rcpts matching - * [Fix] Some more fixes towards emails detection - * [Fix] SpamAssassin: Fail check_freemail_header if regexp didn't match - * [Fix] Use greylisting threshold in greylisting module + * [CritFix] Another portion of tokenization fixes + * [CritFix] Fix memory leak in spf caching logic + * [CritFix] Fix milter commands pipelining + * [CritFix] Fix newlines detection + * [Feature] Filter nan and inf when adding scores + * [Feature] Implement headers flags in mime parser + * [Feature] Support Expires header when using HTTP maps + * [Fix] Actively load skip hashes map in fuzzy storage + * [Fix] Add workaround for IPv6 in sendmail + * [Fix] Authentication Results: Fix SPF smtp.mail_from + * [Fix] Check for magic when checking for an archive + * [Fix] Deal with another case when processing exceptions + * [Fix] Deal with URLs with no slashes after protocol + * [Fix] Do not allow garbadge when checking url domain + * [Fix] Do not ignore short words + * [Fix] Do not strip last character in the last word + * [Fix] Do not treat script content as text + * [Fix] Erase unknown HTML entities + * [Fix] Fix another tokenization issue + * [Fix] Fix DKIM forgeries via multiple headers + * [Fix] Fix emails detection + * [Fix] Fix empty threshold check in greylisting module + * [Fix] Fix enormous scores for R_WHITE_ON_WHITE + * [Fix] Fix loading of per-user redis backend for statistics + * [Fix] Fix multiple headers in DKIM headers list + * [Fix] Fix obscured url in format user@@example.com + * [Fix] Further tokenization fixes + * [Fix] Load skip map from all processes as shared cache is unavailable + * [Fix] Lowercase words + * [Fix] Milter headers: skip_local / skip_authenticated settings + * [Fix] Milter headers: X-Spamd-Result header if X-Virus ran first + * [Fix] Ratelimit: fix whitelisted_rcpts matching + * [Fix] Some more fixes towards emails detection + * [Fix] SpamAssassin: Fail check_freemail_header if regexp didn't match + * [Fix] Use greylisting threshold in greylisting module 1.6.4: 10 Sep 2017 - * [Feature] Add method to get all content-type attributes in Lua - * [Feature] Add some sanity checks for actions and controller - * [Feature] Allow randomly select User-Agent from a list - * [Feature] Deal with obscured URLs with @ symbols - * [Feature] Milter headers: support adding/removing arbitrary headers from config - * [Fix] Add another workaround to display history properly - * [Fix] Add missing rspamadm control options to help - * [Fix] Auth-Results: Multiple DKIM signatures - * [Fix] Crash in URL processing - * [Fix] Default monitoring domain for surbl plugin - * [Fix] Detection of maillist optimized and fixed - * [Fix] Do not cache SPF records with PTR elements - * [Fix] Fix blacklists and DMARC in whitelist - * [Fix] Fix exceptions list in surbl - * [Fix] Fix processing of closed tags - * [Fix] Fix PTR processing in SPF - * [Fix] Lowercase HTTP headers to make them searchable from Lua - * [Fix] options.local_networks setting - * [Fix] Ratelimit: lowercase email addresses - * [Fix] Rebalance and slightly rework MX check plugin - * [Fix] Redis script loading in DMARC; URL tags; URL reputation - * [Fix] Reject invalid bh for DKIM signatures earlier - * [Fix] Remove incorrect method `task:set_metric_subject` - * [Fix] Rewriting subjects via force actions module - * [Fix] RPM postinstall - * [Fix] Treat 'rewrite subject' as spam action - * [Fix] Try harder to find urls - * [Fix] Use full URL when making an HTTP request - * [Fix] Use raw urls when sending requests to redirector - * [Fix] Use weight from map for fuzzy scoring - * [Rules] Penalise R_BAD_CTE_7BIT for utf8 messages + * [Feature] Add method to get all content-type attributes in Lua + * [Feature] Add some sanity checks for actions and controller + * [Feature] Allow randomly select User-Agent from a list + * [Feature] Deal with obscured URLs with @ symbols + * [Feature] Milter headers: support adding/removing arbitrary headers from config + * [Fix] Add another workaround to display history properly + * [Fix] Add missing rspamadm control options to help + * [Fix] Auth-Results: Multiple DKIM signatures + * [Fix] Crash in URL processing + * [Fix] Default monitoring domain for surbl plugin + * [Fix] Detection of maillist optimized and fixed + * [Fix] Do not cache SPF records with PTR elements + * [Fix] Fix blacklists and DMARC in whitelist + * [Fix] Fix exceptions list in surbl + * [Fix] Fix processing of closed tags + * [Fix] Fix PTR processing in SPF + * [Fix] Lowercase HTTP headers to make them searchable from Lua + * [Fix] options.local_networks setting + * [Fix] Ratelimit: lowercase email addresses + * [Fix] Rebalance and slightly rework MX check plugin + * [Fix] Redis script loading in DMARC; URL tags; URL reputation + * [Fix] Reject invalid bh for DKIM signatures earlier + * [Fix] Remove incorrect method `task:set_metric_subject` + * [Fix] Rewriting subjects via force actions module + * [Fix] RPM postinstall + * [Fix] Treat 'rewrite subject' as spam action + * [Fix] Try harder to find urls + * [Fix] Use full URL when making an HTTP request + * [Fix] Use raw urls when sending requests to redirector + * [Fix] Use weight from map for fuzzy scoring + * [Rules] Penalise R_BAD_CTE_7BIT for utf8 messages 1.6.3: 26 Jul 2017 - * [CritFix] Fix semicolons parsing in the content type - * [Feature] Add EBL to the default config - * [Feature] Allow to configure monitored - * [Feature] Allow to skip specific hashes in fuzzy storage - * [Feature] Multimap: checking of symbol options - * [Feature] Redis settings: support checking multiple keys - * [Fix] ARC: Fix Lua 5.3 compatibility; timestamp should be integer - * [Fix] Avoid changing content-transfer-encoding header's value - * [Fix] Don't use whitelist/greylist maps as regexp, but as map - * [Fix] Fix get_content method - * [Fix] Header checks: Fix get_raw_header method - * [Fix] Header checks: REPLYTO_UNPARSEABLE rule - * [Fix] Lua_http: freeing - * [Fix] Milter headers: custom headers: removing headers - * [Fix] Parse HREF urls without explicit prefix - * [Fix] WHITE_ON_WHITE: Ensure score is matched to part that fired the rule - * [WebUI] Escape strings inside HTML in history + * [CritFix] Fix semicolons parsing in the content type + * [Feature] Add EBL to the default config + * [Feature] Allow to configure monitored + * [Feature] Allow to skip specific hashes in fuzzy storage + * [Feature] Multimap: checking of symbol options + * [Feature] Redis settings: support checking multiple keys + * [Fix] ARC: Fix Lua 5.3 compatibility; timestamp should be integer + * [Fix] Avoid changing content-transfer-encoding header's value + * [Fix] Don't use whitelist/greylist maps as regexp, but as map + * [Fix] Fix get_content method + * [Fix] Header checks: Fix get_raw_header method + * [Fix] Header checks: REPLYTO_UNPARSEABLE rule + * [Fix] Lua_http: freeing + * [Fix] Milter headers: custom headers: removing headers + * [Fix] Parse HREF urls without explicit prefix + * [Fix] WHITE_ON_WHITE: Ensure score is matched to part that fired the rule + * [WebUI] Escape strings inside HTML in history 1.6.2: 08 Jul 2017 - * [Conf] Remove Rambler email bl for now - * [Conf] Switch RAMBLER_URIBL to a locally managed source - * [CritFix] Switch from ragel to C for Content-Type parsing - * [Feature] Add `-e` option for lua_repl - * [Feature] Add per-domain emails normalisation rules - * [Feature] Add sessions cache to debug dangling sessions - * [Feature] Add short_text_direct_hash for fuzzy check module - * [Feature] Add text_part:get_stats function - * [Feature] Allow to add custom processing script for surbl - * [Feature] Allow to check reply-to email - * [Feature] Allow to customize spam header, remove existing spam headers - * [Feature] Allow to disable specific workers in the config - * [Feature] Allow to discard messages instead of rejection - * [Feature] Allow to specify custom delimiter in emails plugin - * [Feature] Allow to specify custom User-Agent for rspamc - * [Feature] Allow to store symbols data in Clickhouse - * [Feature] Allow to use HTTPS when connecting to Clickhouse - * [Feature] Enable sessions cache tracking for milter connections - * [Feature] Implement per-line mode in lua_repl (like `perl -p`) - * [Feature] Implement rdns-curve plugin based on rspamd cryptobox - * [Feature] Improve maps cached data lifetime - * [Feature] Improve maps checking frequency - * [Feature] Improve monitored timeouts logic - * [Feature] milter_headers: add `extended_headers_rcpt` option - * [Feature] Milter headers: Add X-Spam-Flag to rmilter-compatibility headers - * [Feature] Milter headers: remove-header routine - * [Feature] Multimap: received filters for extracting TLDs from hostnames - * [Feature] Normalize email aliases in emails module - * [Feature] Re-add rambler email bl (as hashed list) - * [Feature] Reload file maps more frequently - * [Feature] Rework newlines strip parser one more time - * [Feature] Skip updates for messages scanned via controller - * [Feature] Split long DKIM public keys - * [Feature] Store more data when stripping newlines - * [Feature] Support SPF macros transformations - * [Feature] Support suppressing DMARC reports for some domains - * [Fix] Add missing `break` statement - * [Fix] Allow modifiers in SPF macros - * [Fix] DKIM sign tools: edge-cases around use_esld - * [Fix] Do not cache SPF records with macros - * [Fix] Do not overwrite score when setting pre-action - * [Fix] Fix comparison logic - * [Fix] Fix DKIM base64 folding for milter flagged messages - * [Fix] Fix emails module configuration - * [Fix] Fix folding for arc headers when milter interface is used - * [Fix] Fix gmail dots removal - * [Fix] Fix rspamc detection in greylist module - * [Fix] Fix some more issues with HTTP maps - * [Fix] Milter sessions can live forever - * [Fix] Normalize fuzzy probability better - * [Fix] Plug memory leak - * [Fix] RBL: Fixed hashed email address lookups - * [Fix] Try to deal with brain-damaged milter behaviour - * [Fix] Use `\n` to fold headers for milter - * [Rework] Allow to use custom callback for monitored checks - * [Rework] Further steps towards one process monitoring - * [Rework] Send health checks from a single worker - * [WebUI] Round-up throughput summary values + * [Conf] Remove Rambler email bl for now + * [Conf] Switch RAMBLER_URIBL to a locally managed source + * [CritFix] Switch from ragel to C for Content-Type parsing + * [Feature] Add `-e` option for lua_repl + * [Feature] Add per-domain emails normalisation rules + * [Feature] Add sessions cache to debug dangling sessions + * [Feature] Add short_text_direct_hash for fuzzy check module + * [Feature] Add text_part:get_stats function + * [Feature] Allow to add custom processing script for surbl + * [Feature] Allow to check reply-to email + * [Feature] Allow to customize spam header, remove existing spam headers + * [Feature] Allow to disable specific workers in the config + * [Feature] Allow to discard messages instead of rejection + * [Feature] Allow to specify custom delimiter in emails plugin + * [Feature] Allow to specify custom User-Agent for rspamc + * [Feature] Allow to store symbols data in Clickhouse + * [Feature] Allow to use HTTPS when connecting to Clickhouse + * [Feature] Enable sessions cache tracking for milter connections + * [Feature] Implement per-line mode in lua_repl (like `perl -p`) + * [Feature] Implement rdns-curve plugin based on rspamd cryptobox + * [Feature] Improve maps cached data lifetime + * [Feature] Improve maps checking frequency + * [Feature] Improve monitored timeouts logic + * [Feature] milter_headers: add `extended_headers_rcpt` option + * [Feature] Milter headers: Add X-Spam-Flag to rmilter-compatibility headers + * [Feature] Milter headers: remove-header routine + * [Feature] Multimap: received filters for extracting TLDs from hostnames + * [Feature] Normalize email aliases in emails module + * [Feature] Re-add rambler email bl (as hashed list) + * [Feature] Reload file maps more frequently + * [Feature] Rework newlines strip parser one more time + * [Feature] Skip updates for messages scanned via controller + * [Feature] Split long DKIM public keys + * [Feature] Store more data when stripping newlines + * [Feature] Support SPF macros transformations + * [Feature] Support suppressing DMARC reports for some domains + * [Fix] Add missing `break` statement + * [Fix] Allow modifiers in SPF macros + * [Fix] DKIM sign tools: edge-cases around use_esld + * [Fix] Do not cache SPF records with macros + * [Fix] Do not overwrite score when setting pre-action + * [Fix] Fix comparison logic + * [Fix] Fix DKIM base64 folding for milter flagged messages + * [Fix] Fix emails module configuration + * [Fix] Fix folding for arc headers when milter interface is used + * [Fix] Fix gmail dots removal + * [Fix] Fix rspamc detection in greylist module + * [Fix] Fix some more issues with HTTP maps + * [Fix] Milter sessions can live forever + * [Fix] Normalize fuzzy probability better + * [Fix] Plug memory leak + * [Fix] RBL: Fixed hashed email address lookups + * [Fix] Try to deal with brain-damaged milter behaviour + * [Fix] Use `\n` to fold headers for milter + * [Rework] Allow to use custom callback for monitored checks + * [Rework] Further steps towards one process monitoring + * [Rework] Send health checks from a single worker + * [WebUI] Round-up throughput summary values 1.6.1: 14 Jun 2017 - * [Fix] Allow to init resolver without rspamd_config - * [Fix] Do not crash when resolver failed to initialize - * [Fix] Fix abstract context layout - * [Fix] Fix CGP helper reply parsing - * [Fix] Fix crashes when socket write errors occur - * [Fix] Fix parsing IPv6 nameservers in resolv.conf - * [Fix] Milter: Don't defer on "greylist" action + * [Fix] Allow to init resolver without rspamd_config + * [Fix] Do not crash when resolver failed to initialize + * [Fix] Fix abstract context layout + * [Fix] Fix CGP helper reply parsing + * [Fix] Fix crashes when socket write errors occur + * [Fix] Fix parsing IPv6 nameservers in resolv.conf + * [Fix] Milter: Don't defer on "greylist" action 1.6.0: 12 Jun 2017 - * [Conf] Add rspamd_proxy to the default configuration set - * [Conf] Add sample arc module config - * [Conf] Do away with systemd specifics completely - * [Conf] Increase min_bytes to avoid FP - * [Conf] Remove ratelimits from default configuration - * [CritFix] Fix accepting on IPv6 sockets - * [CritFix] Fix corruption when multiple fuzzy are defined - * [CritFix] Fix learn condition in fuzzy check - * [CritFix] Fix memory leak in fuzzy check - * [CritFix] Fix memory leak in maps scheduling - * [CritFix] Paese the last character in DKIM signature correctly - * [CritFix] Zero fill sockaddr_un - * [Feature] Add ability to add doc strings by example - * [Feature] Add API to verify DKIM (and ARC) signatures - * [Feature] Add compression/decompression to proxy - * [Feature] Add count to url structure - * [Feature] Add initial support of the new protocol reply - * [Feature] Add Lua plugin spamtrap - * [Feature] Add `monitored_address` for rbls - * [Feature] Add new schema for bayes tokens - * [Feature] Add preliminary ARC support to dkim code - * [Feature] Add preliminary support of ARC signing - * [Feature] Add rules to detect bad 8bit characters in From and To - * [Feature] Add scanning support for milter protocol - * [Feature] Add support for bidirectional symbols in rspamd_stats - * [Feature] Add support for static maps - * [Feature] Add support of maps with multiple regexps matches - * [Feature] Add `text_multiplier` param - * [Feature] Add the preliminary ARC plugin - * [Feature] Add top redirector targets rank - * [Feature] Allow async events to be registered from LUA rules - * [Feature] Allow storing bayes tokens in Redis - * [Feature] Allow to exclude specific domains from mx check - * [Feature] Allow to have a stack of watcher finalisers - * [Feature] Allow to pass hostname to `-i` flag in Rspamc - * [Feature] Allow to set custom user agent in url redirector - * [Feature] Allow to use custom callback when parsing resolv.conf - * [Feature] Allow to use domain from authenticated user - * [Feature] Bayes expiry plugin - * [Feature] Check dkim sign keys for modifications - * [Feature] DKIM signing: sign_networks/local address specific use_domain settings - * [Feature] DMARC: Support excluding domains from sampling - * [Feature] Expire processing items for URL redirector aggressively - * [Feature] Fix surbl monitored for IP lists, add `monitored_domain` option - * [Feature] Implement caching for dkim body hashes - * [Feature] Implement milter protocol scan reply - * [Feature] Improve omograph phishing detection - * [Feature] Initial support of self-scan in Rspamd proxy - * [Feature] Keep track of headers in milter interface - * [Feature] Milter headers: better controls for local/authenticated - * [Feature] Multimap: email:domain:tld filter - * [Feature] Preliminary DMARC reporting implementation - * [Feature] Reuse stemmers in the cache - * [Feature] Rework confighelp to load Lua plugins - * [Feature] Rework hfilter to use hyperscan if possible - * [Feature] Rework lua RSA API - * [Feature] Rmilter_headers: approximate rmilter's extended_spam_headers - * [Feature] Start integration of milter support in proxy - * [Feature] Store average words length and short words count - * [Feature] Store hash of headers order and names - * [Feature] Support MTA name header - * [Feature] Support multiple types of dkim signing in Lua - * [Feature] Support numeric arguments for Redis requests - * [Feature] Use headers hash in bayes metatokens - * [Feature] Use normal resolv.conf rules of rotation in Rspamd - * [Feature] Use version 2 proto for checking messages - * [Fix] Allow to follow symlinks when safe - * [Fix] Append MX name for authentication results as required - * [Fix] Change default text multiplier from 0.5 to 2.0 - * [Fix] Check min_bytes for images as well - * [Fix] Deal with 7bit charsets properly - * [Fix] Deal with 8bit characters in email addresses - * [Fix] Deal with unpaired <a> tags - * [Fix] Detect confighelp in plugins initialisation - * [Fix] Disable certain checks for utf spoof detection - * [Fix] DKIM Signing: avoid nil index when From header is missing - * [Fix] Do not add exact hashes from different parts - * [Fix] Do not check DMARC if SPF or DKIM were not checked - * [Fix] Do not check URLs that are resolved to be redirected - * [Fix] Do not set bayes probability if we don't use it - * [Fix] Do not stop on illegal unicode points - replace them - * [Fix] Fix another race condition in arc checks - * [Fix] Fix arc count logic - * [Fix] Fix ARC signing - * [Fix] Fix brain-damaged spamc protocol for now - * [Fix] Fix calling for peak functions - * [Fix] Fix couple of issues in FORWARDED rule - * [Fix] Fix CTE propagation from parent containers to children parts - * [Fix] Fix errors processing in the controller - * [Fix] Fix format string in milter - * [Fix] Fix issues in SPF macros parsing - * [Fix] Fix logging format string - * [Fix] Fix logic of cached passwords check - * [Fix] Fix lowercasing of stemmed words - * [Fix] Fix LRU elements removal - * [Fix] Fix memory leak when accepting from unix sockets - * [Fix] Fix milter connections persistence - * [Fix] Fix objects merging in UCL - * [Fix] Fix order of operations to avoid race condition - * [Fix] Fix parsing of long regexp types - * [Fix] Fix passing data to log helper when many symbols defined - * [Fix] Fix pools management for milter session - * [Fix] Fix processing of the watchers - * [Fix] Fix queue id macro in milter - * [Fix] Fix R_BAD_CTE_7BIT rule - * [Fix] Fix Redis timeout set - * [Fix] Fix REPLYTO_UNPARSEABLE rule - * [Fix] Fix setting of email address - * [Fix] Fix some more issues about duplicated fuzzy requests - * [Fix] Fix spamc support in rspamd proxy - * [Fix] Fix syntax error in spamtrap plugin - * [Fix] Fix url counts for href urls - * [Fix] Fix url handling in the protocol - * [Fix] Multimap: Received IP filters with Redis - * [Fix] Oops, fix d9d0fa5e86db2f4470d34395a233b450478b2f60 - * [Fix] Parse rgb[a](x,x,x[,x]) css colors - * [Fix] Phishing: strict_domains - * [Fix] Reduce maps aggressiveness - * [Fix] Reresolve upstreams even if there is a single server there - * [Fix] Rspamadm grep: Disable Lua patterns in string search by default - * [Fix] Skip text parts when checking binary parts in fuzzy check - * [Fix] Support v2 checks in controller - * [Fix] Treat empty address as valid - * [Fix] Try harder to detect CTE - * [Fix] Try to deal with v4 mapped to v6 addresses on accept - * [Fix] Use dkim signing callback properly - * [Fix] Use non-volatile memory for storing data - * [Fix] Use static maps instead of ugly hack for radix_from_config - * [Fix] Use the same pool for related sessions - * [Rework] Continue modularisation for lua library - * [Rework] Initial milter protocol support - * [Rework] Make log pipes worker agnostic, add scanners API - * [Rework] Move authentication results generation to a separate routine - * [Rework] Move common DKIM functions to a separate lua module - * [Rework] Move global functions to a separate directory - * [Rework] Prepare dkim module for ARC checks - * [Rework] Propagate ucl variables from the command line - * [Rework] Remove multiple metrics support from Rspamd - * [Rework] Stop using name 'rmilter' for the modern protocol - * [Rework] Use LFU algorithm in LRU cache - * [Rules] Fix received TLS rules - * [Rules] Improve URL_COUNT_ODD rule - * [WebUI] Fix add header filter in history - * [WebUI] Use modern protocol for checking messages + * [Conf] Add rspamd_proxy to the default configuration set + * [Conf] Add sample arc module config + * [Conf] Do away with systemd specifics completely + * [Conf] Increase min_bytes to avoid FP + * [Conf] Remove ratelimits from default configuration + * [CritFix] Fix accepting on IPv6 sockets + * [CritFix] Fix corruption when multiple fuzzy are defined + * [CritFix] Fix learn condition in fuzzy check + * [CritFix] Fix memory leak in fuzzy check + * [CritFix] Fix memory leak in maps scheduling + * [CritFix] Paese the last character in DKIM signature correctly + * [CritFix] Zero fill sockaddr_un + * [Feature] Add ability to add doc strings by example + * [Feature] Add API to verify DKIM (and ARC) signatures + * [Feature] Add compression/decompression to proxy + * [Feature] Add count to url structure + * [Feature] Add initial support of the new protocol reply + * [Feature] Add Lua plugin spamtrap + * [Feature] Add `monitored_address` for rbls + * [Feature] Add new schema for bayes tokens + * [Feature] Add preliminary ARC support to dkim code + * [Feature] Add preliminary support of ARC signing + * [Feature] Add rules to detect bad 8bit characters in From and To + * [Feature] Add scanning support for milter protocol + * [Feature] Add support for bidirectional symbols in rspamd_stats + * [Feature] Add support for static maps + * [Feature] Add support of maps with multiple regexps matches + * [Feature] Add `text_multiplier` param + * [Feature] Add the preliminary ARC plugin + * [Feature] Add top redirector targets rank + * [Feature] Allow async events to be registered from LUA rules + * [Feature] Allow storing bayes tokens in Redis + * [Feature] Allow to exclude specific domains from mx check + * [Feature] Allow to have a stack of watcher finalisers + * [Feature] Allow to pass hostname to `-i` flag in Rspamc + * [Feature] Allow to set custom user agent in url redirector + * [Feature] Allow to use custom callback when parsing resolv.conf + * [Feature] Allow to use domain from authenticated user + * [Feature] Bayes expiry plugin + * [Feature] Check dkim sign keys for modifications + * [Feature] DKIM signing: sign_networks/local address specific use_domain settings + * [Feature] DMARC: Support excluding domains from sampling + * [Feature] Expire processing items for URL redirector aggressively + * [Feature] Fix surbl monitored for IP lists, add `monitored_domain` option + * [Feature] Implement caching for dkim body hashes + * [Feature] Implement milter protocol scan reply + * [Feature] Improve omograph phishing detection + * [Feature] Initial support of self-scan in Rspamd proxy + * [Feature] Keep track of headers in milter interface + * [Feature] Milter headers: better controls for local/authenticated + * [Feature] Multimap: email:domain:tld filter + * [Feature] Preliminary DMARC reporting implementation + * [Feature] Reuse stemmers in the cache + * [Feature] Rework confighelp to load Lua plugins + * [Feature] Rework hfilter to use hyperscan if possible + * [Feature] Rework lua RSA API + * [Feature] Rmilter_headers: approximate rmilter's extended_spam_headers + * [Feature] Start integration of milter support in proxy + * [Feature] Store average words length and short words count + * [Feature] Store hash of headers order and names + * [Feature] Support MTA name header + * [Feature] Support multiple types of dkim signing in Lua + * [Feature] Support numeric arguments for Redis requests + * [Feature] Use headers hash in bayes metatokens + * [Feature] Use normal resolv.conf rules of rotation in Rspamd + * [Feature] Use version 2 proto for checking messages + * [Fix] Allow to follow symlinks when safe + * [Fix] Append MX name for authentication results as required + * [Fix] Change default text multiplier from 0.5 to 2.0 + * [Fix] Check min_bytes for images as well + * [Fix] Deal with 7bit charsets properly + * [Fix] Deal with 8bit characters in email addresses + * [Fix] Deal with unpaired <a> tags + * [Fix] Detect confighelp in plugins initialisation + * [Fix] Disable certain checks for utf spoof detection + * [Fix] DKIM Signing: avoid nil index when From header is missing + * [Fix] Do not add exact hashes from different parts + * [Fix] Do not check DMARC if SPF or DKIM were not checked + * [Fix] Do not check URLs that are resolved to be redirected + * [Fix] Do not set bayes probability if we don't use it + * [Fix] Do not stop on illegal unicode points - replace them + * [Fix] Fix another race condition in arc checks + * [Fix] Fix arc count logic + * [Fix] Fix ARC signing + * [Fix] Fix brain-damaged spamc protocol for now + * [Fix] Fix calling for peak functions + * [Fix] Fix couple of issues in FORWARDED rule + * [Fix] Fix CTE propagation from parent containers to children parts + * [Fix] Fix errors processing in the controller + * [Fix] Fix format string in milter + * [Fix] Fix issues in SPF macros parsing + * [Fix] Fix logging format string + * [Fix] Fix logic of cached passwords check + * [Fix] Fix lowercasing of stemmed words + * [Fix] Fix LRU elements removal + * [Fix] Fix memory leak when accepting from unix sockets + * [Fix] Fix milter connections persistence + * [Fix] Fix objects merging in UCL + * [Fix] Fix order of operations to avoid race condition + * [Fix] Fix parsing of long regexp types + * [Fix] Fix passing data to log helper when many symbols defined + * [Fix] Fix pools management for milter session + * [Fix] Fix processing of the watchers + * [Fix] Fix queue id macro in milter + * [Fix] Fix R_BAD_CTE_7BIT rule + * [Fix] Fix Redis timeout set + * [Fix] Fix REPLYTO_UNPARSEABLE rule + * [Fix] Fix setting of email address + * [Fix] Fix some more issues about duplicated fuzzy requests + * [Fix] Fix spamc support in rspamd proxy + * [Fix] Fix syntax error in spamtrap plugin + * [Fix] Fix url counts for href urls + * [Fix] Fix url handling in the protocol + * [Fix] Multimap: Received IP filters with Redis + * [Fix] Oops, fix d9d0fa5e86db2f4470d34395a233b450478b2f60 + * [Fix] Parse rgb[a](x,x,x[,x]) css colors + * [Fix] Phishing: strict_domains + * [Fix] Reduce maps aggressiveness + * [Fix] Reresolve upstreams even if there is a single server there + * [Fix] Rspamadm grep: Disable Lua patterns in string search by default + * [Fix] Skip text parts when checking binary parts in fuzzy check + * [Fix] Support v2 checks in controller + * [Fix] Treat empty address as valid + * [Fix] Try harder to detect CTE + * [Fix] Try to deal with v4 mapped to v6 addresses on accept + * [Fix] Use dkim signing callback properly + * [Fix] Use non-volatile memory for storing data + * [Fix] Use static maps instead of ugly hack for radix_from_config + * [Fix] Use the same pool for related sessions + * [Rework] Continue modularisation for lua library + * [Rework] Initial milter protocol support + * [Rework] Make log pipes worker agnostic, add scanners API + * [Rework] Move authentication results generation to a separate routine + * [Rework] Move common DKIM functions to a separate lua module + * [Rework] Move global functions to a separate directory + * [Rework] Prepare dkim module for ARC checks + * [Rework] Propagate ucl variables from the command line + * [Rework] Remove multiple metrics support from Rspamd + * [Rework] Stop using name 'rmilter' for the modern protocol + * [Rework] Use LFU algorithm in LRU cache + * [Rules] Fix received TLS rules + * [Rules] Improve URL_COUNT_ODD rule + * [WebUI] Fix add header filter in history + * [WebUI] Use modern protocol for checking messages 1.5.9: - * [Conf] Increase min_bytes to avoid FP - * [Conf] Remove ratelimits from default configuration - * [CritFix] Fix accepting on IPv6 sockets - * [CritFix] Zero fill sockaddr_un - * [Feature] Add `text_multiplier` param - * [Fix] Check min_bytes for images as well - * [Fix] Do not add exact hashes from different parts - * [Fix] Fix memory leak when accepting from unix sockets - * [Fix] Fix some more issues about duplicated fuzzy requests - * [Fix] Phishing: strict_domains - * [Fix] Skip text parts when checking binary parts in fuzzy check - * [Minor] Add the same duplicates protection for all fuzzy hashes types - * [Minor] Fix braces - * [Minor] Fix test - * [Minor] SPOOF_DISPLAY_NAME: Use all SMTP/MIME recipients - * [Minor] Validate assumed spoofed display name domains to contain a dot + * [Conf] Increase min_bytes to avoid FP + * [Conf] Remove ratelimits from default configuration + * [CritFix] Fix accepting on IPv6 sockets + * [CritFix] Zero fill sockaddr_un + * [Feature] Add `text_multiplier` param + * [Fix] Check min_bytes for images as well + * [Fix] Do not add exact hashes from different parts + * [Fix] Fix memory leak when accepting from unix sockets + * [Fix] Fix some more issues about duplicated fuzzy requests + * [Fix] Phishing: strict_domains + * [Fix] Skip text parts when checking binary parts in fuzzy check + * [Minor] Add the same duplicates protection for all fuzzy hashes types + * [Minor] Fix braces + * [Minor] Fix test + * [Minor] SPOOF_DISPLAY_NAME: Use all SMTP/MIME recipients + * [Minor] Validate assumed spoofed display name domains to contain a dot 1.5.8: - * [CritFix] Fix memory leak in fuzzy check - * [CritFix] Fix memory leak in maps scheduling - * [Feature] Multimap: email:domain:tld filter - * [Fix] DKIM Signing: avoid nil index when From header is missing - * [Fix] Do not set bayes probability if we don't use it - * [Fix] Do not stop on illegal unicode points - replace them - * [Fix] Fix brain-damaged spamc protocol for now - * [Fix] Fix Redis timeout set - * [Fix] Fix spamc support in rspamd proxy - * [Fix] Multimap: Received IP filters with Redis - * [Fix] Parse rgb[a](x,x,x[,x]) css colors - * [Fix] Reresolve upstreams even if there is a single server there - * [Fix] Treat empty address as valid - * [Fix] Try harder to detect CTE - * [Fix] Try to deal with v4 mapped to v6 addresses on accept - * [Minor] Add `wsf` and `hta` bad archive extensions - * [Minor] Fix configuration option - * [Minor] Fix result parsing for SAVAPI - * [Minor] Further logging improvements - * [Minor] Improve logging of errors - * [Minor] Prevent MID_CONTAINS_FROM from firing on empty address - * [Minor] Reduce digit->number transmission penalty - * [Minor] Relax CTYPE_MISSING_DISPOSITION rule + * [CritFix] Fix memory leak in fuzzy check + * [CritFix] Fix memory leak in maps scheduling + * [Feature] Multimap: email:domain:tld filter + * [Fix] DKIM Signing: avoid nil index when From header is missing + * [Fix] Do not set bayes probability if we don't use it + * [Fix] Do not stop on illegal unicode points - replace them + * [Fix] Fix brain-damaged spamc protocol for now + * [Fix] Fix Redis timeout set + * [Fix] Fix spamc support in rspamd proxy + * [Fix] Multimap: Received IP filters with Redis + * [Fix] Parse rgb[a](x,x,x[,x]) css colors + * [Fix] Reresolve upstreams even if there is a single server there + * [Fix] Treat empty address as valid + * [Fix] Try harder to detect CTE + * [Fix] Try to deal with v4 mapped to v6 addresses on accept + * [Minor] Add `wsf` and `hta` bad archive extensions + * [Minor] Fix configuration option + * [Minor] Fix result parsing for SAVAPI + * [Minor] Further logging improvements + * [Minor] Improve logging of errors + * [Minor] Prevent MID_CONTAINS_FROM from firing on empty address + * [Minor] Reduce digit->number transmission penalty + * [Minor] Relax CTYPE_MISSING_DISPOSITION rule 1.5.7: - * [CritFix] Fix corruption when multiple fuzzy are defined - * [CritFix] Fix learn condition in fuzzy check - * [Feature] Add rules to detect bad 8bit characters in From and To - * [Feature] DKIM signing: sign_networks/local address specific use_domain settings - * [Feature] Support numeric arguments for Redis requests - * [Fix] Deal with 8bit characters in email addresses - * [Fix] Fix couple of issues in FORWARDED rule - * [Fix] Fix passing data to log helper when many symbols defined - * [Fix] Fix R_BAD_CTE_7BIT rule - * [Fix] Fix REPLYTO_UNPARSEABLE rule - * [Fix] Fix setting of email address - * [Fix] Rspamadm grep: Disable Lua patterns in string search by default - * [Minor] Add Lua 5.3 workaround - * [Minor] Add lua methods to detect if a part has 8bit characters - * [Minor] Allow session-less lua dns requests - * [Minor] Allow to append greylist end time to message reported - * [Minor] Avoid `nil` table - * [Minor] Disable dkim_signing if redis is specified but not configured - * [Minor] Fix build with pcre2 - * [Minor] Fix rule - * [Minor] Fix warnings - * [Minor] Format floating point number - * [Minor] Push email flags to the lua API - * [Minor] Silence some warnings - * [Minor] Silence warning - * [Minor] Try all hostname regexps to find the most significant one - * [WebUI] Fix add header filter in history + * [CritFix] Fix corruption when multiple fuzzy are defined + * [CritFix] Fix learn condition in fuzzy check + * [Feature] Add rules to detect bad 8bit characters in From and To + * [Feature] DKIM signing: sign_networks/local address specific use_domain settings + * [Feature] Support numeric arguments for Redis requests + * [Fix] Deal with 8bit characters in email addresses + * [Fix] Fix couple of issues in FORWARDED rule + * [Fix] Fix passing data to log helper when many symbols defined + * [Fix] Fix R_BAD_CTE_7BIT rule + * [Fix] Fix REPLYTO_UNPARSEABLE rule + * [Fix] Fix setting of email address + * [Fix] Rspamadm grep: Disable Lua patterns in string search by default + * [Minor] Add Lua 5.3 workaround + * [Minor] Add lua methods to detect if a part has 8bit characters + * [Minor] Allow session-less lua dns requests + * [Minor] Allow to append greylist end time to message reported + * [Minor] Avoid `nil` table + * [Minor] Disable dkim_signing if redis is specified but not configured + * [Minor] Fix build with pcre2 + * [Minor] Fix rule + * [Minor] Fix warnings + * [Minor] Format floating point number + * [Minor] Push email flags to the lua API + * [Minor] Silence some warnings + * [Minor] Silence warning + * [Minor] Try all hostname regexps to find the most significant one + * [WebUI] Fix add header filter in history 1.5.6: - * [Feature] Add unigramms support in bayes - * [Feature] Allow configurable sign headers for DKIM - * [Feature] Allow to add unigramm metatokens from Lua - * [Feature] DKIM Signing: envelope match exception for local IPs - * [Feature] UCL: register parser variables from Lua - * [Fix] Always try to adjust filename - * [Fix] Do extra copy to ensure that original content is never touched - * [Fix] Fix SPOOF_REPLYTO rule - * [Fix] Ignore Rmilter added Received - * [Fix] More fixes for hashed email dnsbls - * [Fix] Plug memory leak in chartable module - * [WebUI] Display multiple alerts at once + * [Feature] Add unigramms support in bayes + * [Feature] Allow configurable sign headers for DKIM + * [Feature] Allow to add unigramm metatokens from Lua + * [Feature] DKIM Signing: envelope match exception for local IPs + * [Feature] UCL: register parser variables from Lua + * [Fix] Always try to adjust filename + * [Fix] Do extra copy to ensure that original content is never touched + * [Fix] Fix SPOOF_REPLYTO rule + * [Fix] Ignore Rmilter added Received + * [Fix] More fixes for hashed email dnsbls + * [Fix] Plug memory leak in chartable module + * [WebUI] Display multiple alerts at once 1.5.5: - * [CritFix] Fix classifier learning with Redis backend - * [CritFix] Fix issue when parsing encoded rfc822/messages - * [Feature] Add escaped version of lua_ucl import - * [Feature] Add task:headers_foreach function - * [Feature] Allow to process filenames from content type - * [Feature] Allow to query hashed emails - * [Feature] Ignore bayes with mostly metatokens or with too few text - * [Feature] Probabilistically skip metatokens - * [Feature] Retrieve all virus names from SAVAPI - * [Feature] Rework classifiers lua metatokens - * [Feature] Store headers order - * [Feature] Store text tokens inside bayes tokens - * [Feature] Use cached shingles keys - * [Fix] Add missing score normalisation for HFILTER_URL_ONLY - * [Fix] Avoid lookup in absent hash - * [Fix] Check return values from Lua functions called from C - * [Fix] Do not count sending and loading time in rspamc - * [Fix] Escape json strings for controller rejplies from Lua - * [Fix] Fix archive scans for savapi - * [Fix] Fix domain_only emails RBL - * [Fix] Fix ip_score map configuration - * [Fix] Fix JSON output for history_redis - * [Fix] Fix one character length substrings search - * [Fix] Fix parsing of non-RFC compatible Exim received - * [Fix] Fix parsing of options for workers with the same type - * [Fix] Fix processing of small tokens vectors - * [Fix] Fix rfc2047 tokenization - * [Fix] Fix typo - * [Fix] More fixes for inplace decoding - * [Fix] Try to avoid modifications of the original data - * [Fix] URL redirector: Fix call to is_redirector - * [Rework] Set token data as uint64_t instead of chars array - * [WebUI] Check if neighbours' history backend versions match - * [WebUI] Disable phrase connectors replacement in history filtering - * [WebUI] Disable phrase connectors replacement in symbols filtering - * [WebUI] Do not hide messages with bad subject, just replace it with '???' - * [WebUI] Fix error message - * [WebUI] Fix history v2 display - * [WebUI] Fix legacy history - * [WebUI] history: break To address lists on commas - * [WebUI] Increase default timeout to 20 seconds - * [WebUI] Save some history table space + * [CritFix] Fix classifier learning with Redis backend + * [CritFix] Fix issue when parsing encoded rfc822/messages + * [Feature] Add escaped version of lua_ucl import + * [Feature] Add task:headers_foreach function + * [Feature] Allow to process filenames from content type + * [Feature] Allow to query hashed emails + * [Feature] Ignore bayes with mostly metatokens or with too few text + * [Feature] Probabilistically skip metatokens + * [Feature] Retrieve all virus names from SAVAPI + * [Feature] Rework classifiers lua metatokens + * [Feature] Store headers order + * [Feature] Store text tokens inside bayes tokens + * [Feature] Use cached shingles keys + * [Fix] Add missing score normalisation for HFILTER_URL_ONLY + * [Fix] Avoid lookup in absent hash + * [Fix] Check return values from Lua functions called from C + * [Fix] Do not count sending and loading time in rspamc + * [Fix] Escape json strings for controller rejplies from Lua + * [Fix] Fix archive scans for savapi + * [Fix] Fix domain_only emails RBL + * [Fix] Fix ip_score map configuration + * [Fix] Fix JSON output for history_redis + * [Fix] Fix one character length substrings search + * [Fix] Fix parsing of non-RFC compatible Exim received + * [Fix] Fix parsing of options for workers with the same type + * [Fix] Fix processing of small tokens vectors + * [Fix] Fix rfc2047 tokenization + * [Fix] Fix typo + * [Fix] More fixes for inplace decoding + * [Fix] Try to avoid modifications of the original data + * [Fix] URL redirector: Fix call to is_redirector + * [Rework] Set token data as uint64_t instead of chars array + * [WebUI] Check if neighbours' history backend versions match + * [WebUI] Disable phrase connectors replacement in history filtering + * [WebUI] Disable phrase connectors replacement in symbols filtering + * [WebUI] Do not hide messages with bad subject, just replace it with '???' + * [WebUI] Fix error message + * [WebUI] Fix history v2 display + * [WebUI] Fix legacy history + * [WebUI] history: break To address lists on commas + * [WebUI] Increase default timeout to 20 seconds + * [WebUI] Save some history table space 1.5.4: - * [Conf] Add history_redis default configuration - * [Feature] Add spoofed rules - * [Feature] Add URL_IN_SUBJECT rule - * [Feature] Allow to get task's subject - * [Feature] Allow to specify maximum number of shots for symbols - * [Feature] Distinguish URLs found in Subject - * [Feature] Memoize LPEG grammars - * [Feature] Parse else parts in SA rules - * [Feature] Process subject for mixed characters - * [Feature] Resolve url chains in url_redirector module - * [Feature] Stat greylisted messages as greylisted not soft-rejected - * [Feature] Support checking for redirector in Lua SURBL - * [Feature] Support tag_exists SA function - * [Feature] Work with broken rfc2047 tokens - * [Fix] Check all watcher's dependencies - * [Fix] Do not compile hyperscan with no SSSE3 support - * [Fix] Do not crash if cannot decode qp encoded part - * [Fix] Fix dependencies of DKIM when multiple signatures are found - * [Fix] Fix lists in whitelist plugin - * [Fix] Fix one-shot symbols weight calculations - * [Fix] Fix options and shots match - * [Fix] Fix order of symbol options - * [Fix] Fix parsing of dot at the end of the address - * [Fix] Fix parsing of lua table arguments - * [Fix] Fix processing of subject words - * [Fix] Fix string split memoization - * [Fix] Fix templates grammar usage - * [Fix] Fix various issues related to Lua stack manipulation - * [Fix] Force actions: Use postfilter if we have honor_action / require_action - * [Fix] Further fixes to avoid PHISHING FP - * [Fix] Preserve order of options in symbols - * [Fix] Rspamadm grep: deal with unusually-formatted logs - * [Fix] Use hostname suffix when dealing with history - * [Rework] Remove outdated SA rules - * [WebUI] Add flexible columns - * [WebUI] Add footable - * [WebUI] Add sender, recipients and subject columns - * [WebUI] Allow message-id break - * [WebUI] Fix history clustering - * [WebUI] Fix history display - * [WebUI] Fix sorting - * [WebUI] Humanize sizes - * [WebUI] Initial move towards footable - * [WebUI] Remove datatables - * [WebUI] Replace `.values` method with `.map` - * [WebUI] Rework v2 symbols display - * [WebUI] Try to normalize frequencies - * [WebUI] Unbreak WebUI - * [WebUI] Use Footable to draw Throughput summary table + * [Conf] Add history_redis default configuration + * [Feature] Add spoofed rules + * [Feature] Add URL_IN_SUBJECT rule + * [Feature] Allow to get task's subject + * [Feature] Allow to specify maximum number of shots for symbols + * [Feature] Distinguish URLs found in Subject + * [Feature] Memoize LPEG grammars + * [Feature] Parse else parts in SA rules + * [Feature] Process subject for mixed characters + * [Feature] Resolve url chains in url_redirector module + * [Feature] Stat greylisted messages as greylisted not soft-rejected + * [Feature] Support checking for redirector in Lua SURBL + * [Feature] Support tag_exists SA function + * [Feature] Work with broken rfc2047 tokens + * [Fix] Check all watcher's dependencies + * [Fix] Do not compile hyperscan with no SSSE3 support + * [Fix] Do not crash if cannot decode qp encoded part + * [Fix] Fix dependencies of DKIM when multiple signatures are found + * [Fix] Fix lists in whitelist plugin + * [Fix] Fix one-shot symbols weight calculations + * [Fix] Fix options and shots match + * [Fix] Fix order of symbol options + * [Fix] Fix parsing of dot at the end of the address + * [Fix] Fix parsing of lua table arguments + * [Fix] Fix processing of subject words + * [Fix] Fix string split memoization + * [Fix] Fix templates grammar usage + * [Fix] Fix various issues related to Lua stack manipulation + * [Fix] Force actions: Use postfilter if we have honor_action / require_action + * [Fix] Further fixes to avoid PHISHING FP + * [Fix] Preserve order of options in symbols + * [Fix] Rspamadm grep: deal with unusually-formatted logs + * [Fix] Use hostname suffix when dealing with history + * [Rework] Remove outdated SA rules + * [WebUI] Add flexible columns + * [WebUI] Add footable + * [WebUI] Add sender, recipients and subject columns + * [WebUI] Allow message-id break + * [WebUI] Fix history clustering + * [WebUI] Fix history display + * [WebUI] Fix sorting + * [WebUI] Humanize sizes + * [WebUI] Initial move towards footable + * [WebUI] Remove datatables + * [WebUI] Replace `.values` method with `.map` + * [WebUI] Rework v2 symbols display + * [WebUI] Try to normalize frequencies + * [WebUI] Unbreak WebUI + * [WebUI] Use Footable to draw Throughput summary table 1.5.3: - * [Conf] Add composite for hacked wordpress phishing - * [CritFix] Fix base64 decoding when there are unparseable characters - * [Feature] Additional symbol metadata in metadata exporter - * [Feature] Add method to get protocol reply from Lua - * [Feature] Add symbols when tagged rcpt/sender are normalised - * [Feature] Add task:get_symbols_all() function - * [Feature] Allow multiple formats of DKIM signing key - * [Feature] Allow to cache and use flexible protocol reply - * [Feature] Allow to set one_shot flag from register_symbol - * [Feature] Allow to skip certain types of hashes when learning fuzzy - * [Feature] Cache and insert scan time into the protocol - * [Feature] Detect newlines in rspamc --mime - * [Feature] DKIM signing: support use of maps - * [Feature] Greylist: Support excluding low-scoring messages from greylisting - * [Feature] Implement lua history in controller - * [Feature] Implement redis history querying - * [Feature] Preliminary implementation of redis history plugin - * [Feature] Support using request headers in settings - * [Fix] Change default template to deal with non-ASCII characters - * [Fix] Deal with lists of maps in whitelist module - * [Fix] DKIM signing: use domain-specific signing key - * [Fix] Do not reallocate completed zstd buffer - * [Fix] Do not use local_addrs in proxy - * [Fix] Fix crash when resolver is undefined - * [Fix] Fix double free when closing lua_tcp connections - * [Fix] Fix for lua 5.3 - * [Fix] Fix freeing of arrays iterators - * [Fix] Fix issue with task:get_symbol and symbols with no metric - * [Fix] Fix log line duplication in `rspamadm grep` - * [Fix] Fix memory corruption on termination - * [Fix] Fix out-of-bound access in base64 decode - * [Fix] Fix ratelimit + greylisting - * [Fix] Fix subject rewriting - * [Fix] Fix task:set_recipients function - * [Fix] Fix URI_COUNT_ODD rule - * [Fix] Follow the traditional symbols conventions in RCPT_COUNT rule - * [Fix] Greylist: Suppress greylist action for whitelisted hosts too - * [Fix] Metadata exporter: use rule-specific settings for emails - * [Fix] Properly set missing fields in exporter - * [Fix] Proxy: max_retries option - * [Fix] RCPT_COUNT fixes - * [Fix] Rework HAS_X_PRIO rule to match symbols conventions - * [Fix] Update issues in ac-trie - * [Fix] Use optimised base64 decoding in DKIM - * [WebUI] Add preliminary v2 history parser - * [WebUI] Allow different history parsers - * [WebUI] Display symbols - * [WebUI] Rework history v2 function + * [Conf] Add composite for hacked wordpress phishing + * [CritFix] Fix base64 decoding when there are unparseable characters + * [Feature] Additional symbol metadata in metadata exporter + * [Feature] Add method to get protocol reply from Lua + * [Feature] Add symbols when tagged rcpt/sender are normalised + * [Feature] Add task:get_symbols_all() function + * [Feature] Allow multiple formats of DKIM signing key + * [Feature] Allow to cache and use flexible protocol reply + * [Feature] Allow to set one_shot flag from register_symbol + * [Feature] Allow to skip certain types of hashes when learning fuzzy + * [Feature] Cache and insert scan time into the protocol + * [Feature] Detect newlines in rspamc --mime + * [Feature] DKIM signing: support use of maps + * [Feature] Greylist: Support excluding low-scoring messages from greylisting + * [Feature] Implement lua history in controller + * [Feature] Implement redis history querying + * [Feature] Preliminary implementation of redis history plugin + * [Feature] Support using request headers in settings + * [Fix] Change default template to deal with non-ASCII characters + * [Fix] Deal with lists of maps in whitelist module + * [Fix] DKIM signing: use domain-specific signing key + * [Fix] Do not reallocate completed zstd buffer + * [Fix] Do not use local_addrs in proxy + * [Fix] Fix crash when resolver is undefined + * [Fix] Fix double free when closing lua_tcp connections + * [Fix] Fix for lua 5.3 + * [Fix] Fix freeing of arrays iterators + * [Fix] Fix issue with task:get_symbol and symbols with no metric + * [Fix] Fix log line duplication in `rspamadm grep` + * [Fix] Fix memory corruption on termination + * [Fix] Fix out-of-bound access in base64 decode + * [Fix] Fix ratelimit + greylisting + * [Fix] Fix subject rewriting + * [Fix] Fix task:set_recipients function + * [Fix] Fix URI_COUNT_ODD rule + * [Fix] Follow the traditional symbols conventions in RCPT_COUNT rule + * [Fix] Greylist: Suppress greylist action for whitelisted hosts too + * [Fix] Metadata exporter: use rule-specific settings for emails + * [Fix] Properly set missing fields in exporter + * [Fix] Proxy: max_retries option + * [Fix] RCPT_COUNT fixes + * [Fix] Rework HAS_X_PRIO rule to match symbols conventions + * [Fix] Update issues in ac-trie + * [Fix] Use optimised base64 decoding in DKIM + * [WebUI] Add preliminary v2 history parser + * [WebUI] Allow different history parsers + * [WebUI] Display symbols + * [WebUI] Rework history v2 function 1.5.2: - * [Conf] Add default config for spamassasssin plugin - * [Conf] Add default configuration for antivirus module - * [Conf] Add dkim signing docs - * [Conf] Add mx_check default config - * [Conf] Add replies config - * [Conf] Add trie default config - * [Feature] Add heuristic to find text parts in files - * [Feature] Add rule to detect broken content type - * [Feature] Allow to extract CTE in Lua API - * [Feature] Allow to set from address for a lua_task - * [Feature] Allow to set recipients of a task from Lua - * [Feature] Enchance text_part:get_content method - * [Feature] Remove + aliases from emails - * [Feature] Support rmilter block and dkim signature in CGP helper - * [Feature] Support running event loop from Lua - * [Fix] Antivirus: use scanner-specific redis prefix - * [Fix] Couple of fixes for DKIM signing module - * [Fix] Distinguish missing and broken mandatory headers - * [Fix] Do more heuristical detection for missing CTE - * [Fix] Do not resort cache on each check - * [Fix] Fix CGP escaping - * [Fix] Fix MISSING_MIME_VERSION rule for plain messages - * [Fix] Fix parsing of cte in expressions - * [Fix] Fix partial matches in rspamadm grep - * [Fix] Fix setting class on style field - * [WebUI] Auto-switch Throughput units to `msg/min` for very low rate - * [WebUI] Update D3Evolution to 0.0.2 + * [Conf] Add default config for spamassasssin plugin + * [Conf] Add default configuration for antivirus module + * [Conf] Add dkim signing docs + * [Conf] Add mx_check default config + * [Conf] Add replies config + * [Conf] Add trie default config + * [Feature] Add heuristic to find text parts in files + * [Feature] Add rule to detect broken content type + * [Feature] Allow to extract CTE in Lua API + * [Feature] Allow to set from address for a lua_task + * [Feature] Allow to set recipients of a task from Lua + * [Feature] Enchance text_part:get_content method + * [Feature] Remove + aliases from emails + * [Feature] Support rmilter block and dkim signature in CGP helper + * [Feature] Support running event loop from Lua + * [Fix] Antivirus: use scanner-specific redis prefix + * [Fix] Couple of fixes for DKIM signing module + * [Fix] Distinguish missing and broken mandatory headers + * [Fix] Do more heuristical detection for missing CTE + * [Fix] Do not resort cache on each check + * [Fix] Fix CGP escaping + * [Fix] Fix MISSING_MIME_VERSION rule for plain messages + * [Fix] Fix parsing of cte in expressions + * [Fix] Fix partial matches in rspamadm grep + * [Fix] Fix setting class on style field + * [WebUI] Auto-switch Throughput units to `msg/min` for very low rate + * [WebUI] Update D3Evolution to 0.0.2 1.5.1: - * [CritFix] Fix processing of stop_patterns with `\0` character - * [CritFix] Fix setting of raw key for signing - * [Fix] Fix lua exports from plugins during reload - * [Fix] Fix prefilters action scores - * [Fix] Fix symbols processing order - * [Minor] Help cmake find gthread - * [Minor] Some cmake fixes + * [CritFix] Fix processing of stop_patterns with `\0` character + * [CritFix] Fix setting of raw key for signing + * [Fix] Fix lua exports from plugins during reload + * [Fix] Fix prefilters action scores + * [Fix] Fix symbols processing order + * [Minor] Help cmake find gthread + * [Minor] Some cmake fixes 1.5.0: - * [Conf] Add configurations for asn, clickhouse and dcc - * [Conf] Add default config for url redirector plugin - * [Conf] Add the default config for greylist module - * [Conf] Allow to edit all local maps from WebUI by default - * [CritFix] Deal with absent headers in DKIM - * [CritFix] Do not trust remote shingles count - * [CritFix] Fix bad memory leak in TLS certificates validation - * [CritFix] Fix critical memory issues with radix maps - * [CritFix] Fix descriptors leak on reload - * [CritFix] Fix headers selection in DKIM verification - * [CritFix] Fix parsing of boundaries that end with `--` - * [CritFix] Repair PTR_ARRAY_FOREACH macro - * [Feature] Add CORS support to the controller - * [Feature] Add FROM_NAME_EXCESS_SPACE rule - * [Feature] Add REPLYTO_EMAIL_HAS_TITLE rule - * [Feature] Add `caseless_hash` method to `lua_util` - * [Feature] Add `rip` keyword to ratelimit module - * [Feature] Add a simple benchmark for content type parsing - * [Feature] Add boundaries parsing in content type - * [Feature] Add charset detection for text parts - * [Feature] Add content disposition parser - * [Feature] Add fallback if too many updates are failing - * [Feature] Add function to convert struct tm to time using timezone - * [Feature] Add function to normalize HTTP paths - * [Feature] Add fuzzy collection plugin - * [Feature] Add fuzzy logic for images - * [Feature] Add gmime parser to mime_tool - * [Feature] Add heuristic to detect broken messages - * [Feature] Add heuristic to find displayed URLs - * [Feature] Add heuristic to process broken email addresses - * [Feature] Add images normalization - * [Feature] Add mechanism for disabling composites (Fixes #1270) - * [Feature] Add method to create regexp from a glob pattern - * [Feature] Add mime encoding manipulation routines - * [Feature] Add mime tool to explore messages - * [Feature] Add more meta tokens from received headers - * [Feature] Add neighbours option to support Rspamd cluster in WebUI - * [Feature] Add new function to parse mime addresses - * [Feature] Add new methods for lua_tcp - * [Feature] Add own headers decoding routine - * [Feature] Add own routine to generate a message id - * [Feature] Add parser for SMTP date - * [Feature] Add per-task lua cache to reuse 'heavy' objects - * [Feature] Add plugins list path in WebUI - * [Feature] Add preliminary multipart support - * [Feature] Add preliminary version of DKIM signing module - * [Feature] Add profiling support in client output - * [Feature] Add rfc2047 grammar - * [Feature] Add rfc2047 variant for QP decoding - * [Feature] Add rmilter_headers module (Fixes #1227) - * [Feature] Add sse42 version of base64 decoding - * [Feature] Add ssse3 and avx2 base64 decoders - * [Feature] Add support of libgd - * [Feature] Add the preliminary version of redirects resolver in Lua - * [Feature] Add ucl_object_iterate_full function - * [Feature] Add url encoding function - * [Feature] Allow SOA requests in lua dns - * [Feature] Allow custom parse types in lua ucl - * [Feature] Allow plugins to register webui handlers - * [Feature] Allow to add options explicitly to symbols - * [Feature] Allow to call a callback when symbol frequency is on peak - * [Feature] Allow to call redirector script from SURBL - * [Feature] Allow to create variable length dkim keys - * [Feature] Allow to have module specific options for Redis in plugins - * [Feature] Allow to pass sign key directly from Lua - * [Feature] Allow to register configuration docs from Lua API - * [Feature] Allow to return options as a table - * [Feature] Allow to set peak callbacks from Lua - * [Feature] Allow to specify custom method for a message - * [Feature] Allow to store dkim keys in Redis - * [Feature] Allow to store messages in files - * [Feature] Apply DCT using AAN for fuzzy signature - * [Feature] Avira SAVAPI support - * [Feature] Cache and simplify DCT and jpeg decode - * [Feature] Cache libicu converters - * [Feature] Detect URLs with suspicious omographs - * [Feature] Do not increase score for duplicate options - * [Feature] Do not trust CTE, check base64 and qp strictly - * [Feature] Dynamic reputation in URL reputation plugin - * [Feature] Extend redis lock when learning spawned - * [Feature] Filter non-utf chars from all decoded headers - * [Feature] Fix phishing detection for IDNA urls - * [Feature] Ignore bad symbols on base64 decoding - * [Feature] Ignore too wide elements in SPF - * [Feature] Implement fuzzy collection mode - * [Feature] Implement helo maps in multimap - * [Feature] Implement human readable buckets configuration - * [Feature] Implement min-hash shingles for DCT data from images - * [Feature] Implement new algorithm for fuzzy hashes of images - * [Feature] Implement new unicode normalizer - * [Feature] Implement quoted printable decoding - * [Feature] Implement received headers flags - * [Feature] Implement rspamdgrep tool - * [Feature] Implement sane checksum for config file - * [Feature] Implement url tags concept - * [Feature] Improve detection of omographs using libicu - * [Feature] Improve url redirector module - * [Feature] Multimap: Received header processing - * [Feature] Multiple improvements in the maps - * [Feature] New URL filters in multimap - * [Feature] Plugin to force actions on selected symbols - * [Feature] RBL module: support hashing for emails and helo RBL - * [Feature] Reuse URL tags in SURBL module - * [Feature] Rework RRD ds count, add conversion path - * [Feature] Rework surbl module to avoid extra redirector calls - * [Feature] Send config id to the WebUI - * [Feature] Simplify HTTPCrypt client support - * [Feature] Skip processing for large images - * [Feature] Start collection only mode implementation for fuzzy storage - * [Feature] Start import of the optimized base64 decode - * [Feature] Store all received headers in lua - * [Feature] Store relational order of all headers in a message - * [Feature] Support DKIM signing in Lua plugins - * [Feature] Support HTTPCrypt client in lua_http - * [Feature] Support setting SMTP message in multimap - * [Feature] Support setting metric subject from Lua - * [Feature] Support setting subject in force actions module - * [Feature] Treat v6 mapped addresses as v4 addresses - * [Feature] URL reputation plugin - * [Feature] Use Redis instead of memcached in URLs redirector - * [Feature] Use Rspamd rfc2047 decoder instead of gmime one - * [Feature] Use a different normalization for fuzzy images - * [Feature] Use normalized images in fuzzy hashes - * [Feature] Use own code for parsing of date - * [Feature] Use shingles for images fuzzying - * [Feature] Use t1ha for hashes, allow inlining - * [Feature] Use t1ha instead of metrohash and xxhash32 - * [Feature] Various new features in metadata exporter module - * [Feature] rmilter_headers: authentication-results (#78) - * [Fix] Add additional check to mark redis connection inactive - * [Fix] Add packed attribute for protocol structure - * [Fix] Adopt OMOGRAPH_URL rule - * [Fix] Allow static maps - * [Fix] Allow to disable classifiers checks using settings and conditions - * [Fix] Another try to fix 0 length maps - * [Fix] Another try to fix corruption during maps reload - * [Fix] Another try to fix descriptors leak - * [Fix] Another try to fix reload and logger - * [Fix] Antivirus module: register virtual symbols for patterns - * [Fix] Avoid extensive reallocs - * [Fix] Avoid mempool leak in SA plugin on reload - * [Fix] Avoid race condition on saving cache and reload - * [Fix] Avoid reusing g_error (Fixes #1262) - * [Fix] Break pool connection on fatal redis errors - * [Fix] Check for NaN properly - * [Fix] Couple of fixes for date parsing - * [Fix] Date header timezone adjustments (#1279) - * [Fix] Deal with EOF properly - * [Fix] Decode filename in content disposition - * [Fix] Disable fuzzy images by default - * [Fix] Disable zero-copy mode for text parts to avoid crashes - * [Fix] Do not destroy session when not all finish scripts are done - * [Fix] Do not greyscale images - * [Fix] Do not leave parent-less workers processes on fatal errors - * [Fix] Do not lowercase Content-Disposition to perform decoding - * [Fix] Do not penalize characters just after numeric prefix - * [Fix] Do not refork workers that are intended to die - * [Fix] Do not set pre-result and update records for no `Queue-ID` messages - * [Fix] Do not skip post-filters when pre-filters have set some results - * [Fix] Do not stop symbols planning if async events are pending - * [Fix] Do not try to set keys for unencrypted requests in proxy - * [Fix] Encode URLs according to rfc3986 - * [Fix] Encode URLs before sending them to the protocol - * [Fix] Filter bad characters from message id - * [Fix] Fix CTE detection heuristic - * [Fix] Fix Content-Type in HTTP requests - * [Fix] Fix IDN eslds phishing checks - * [Fix] Fix adding maps from config in Lua - * [Fix] Fix another reload memory issue - * [Fix] Fix argument returned on redis backend errors - * [Fix] Fix assertion in graph handling - * [Fix] Fix body trie matching - * [Fix] Fix build - * [Fix] Fix byte array expansion during toutf8 conversion - * [Fix] Fix charset normalisation - * [Fix] Fix checking of DKIM bodies that needs just `\n` to be added - * [Fix] Fix couple of cornercases with email addresses - * [Fix] Fix couple of issues - * [Fix] Fix dependencies tracking for callback symbols - * [Fix] Fix detection of jpeg size - * [Fix] Fix errors handling in fuzzy backend initialization - * [Fix] Fix fuzzy hashes count - * [Fix] Fix globbing and convert lists to arrays in fuzzy_check - * [Fix] Fix heuristical CTE detection for QP encoding - * [Fix] Fix ignoring of bad text parts - * [Fix] Fix indexes in array access, interleave loop - * [Fix] Fix int64 -> double conversion - * [Fix] Fix invalid memory access on reload - * [Fix] Fix issues with empty updates - * [Fix] Fix issues with quoted-printable encoding - * [Fix] Fix keys names - * [Fix] Fix lots of issues in mime parser code - * [Fix] Fix lua maps load - * [Fix] Fix macro name - * [Fix] Fix mas group score calculations - * [Fix] Fix matching of the same patterns from different tries - * [Fix] Fix memory corruprtion and leak - * [Fix] Fix memory leak in HTTP maps - * [Fix] Fix memory leak in expression destroying - * [Fix] Fix memory leak in parsing of mime names - * [Fix] Fix memory leak in safe ucl iterators - * [Fix] Fix memory leak on reload in plugins - * [Fix] Fix modules reconfigure on reload - * [Fix] Fix monitored setup fro URLBLs with IP addresses - * [Fix] Fix name of var - * [Fix] Fix new rrd updates - * [Fix] Fix out of bounds access - * [Fix] Fix parsing messages with no body - * [Fix] Fix parsing of '=' character in headers - * [Fix] Fix parsing of messages with no content type - * [Fix] Fix plugins callbacks in webui - * [Fix] Fix possible memory corruption in redis pool - * [Fix] Fix probability calculations for fuzzy redis backend - * [Fix] Fix processing errors in lua_tcp - * [Fix] Fix processing of emails with name only - * [Fix] Fix processing of non-multipart messages - * [Fix] Fix processing of parts with no valid content type - * [Fix] Fix race condition in SIGUSR2 handler - * [Fix] Fix redis options parsing when no redis servers are defined - * [Fix] Fix reload and hyperscan ready event - * [Fix] Fix reload memory issue - * [Fix] Fix rra_ptr conversion - * [Fix] Fix rrd file conversion - * [Fix] Fix setting of content-type attributes - * [Fix] Fix signing headers creation in DKIM - * [Fix] Fix stddev calculations - * [Fix] Fix surbl plugin to work with composite maps - * [Fix] Fix timezones parsing - * [Fix] Fix tokens usage - * [Fix] Fix urls and emails hashes - * [Fix] Fix usage of unsafe ucl iterators - * [Fix] Fix work with broken utf8 tokens - * [Fix] Fix writing of user to roll history - * [Fix] Forgotten worker - * [Fix] Further memory leaks fixes - * [Fix] Ignore lua metatokens in bayes for now - * [Fix] Improve OMOGRAPH_URL rule - * [Fix] Lua IP from string should be invalid if parsing failed - * [Fix] Miltiple fixes to new lua_tcp, add debugging - * [Fix] More fixes for iterators cleanup - * [Fix] More fixes to logger initialization - * [Fix] More heuristic fixes for phishing detection - * [Fix] More leaks eliminated - * [Fix] More leaks... - * [Fix] More random fixes for reload... - * [Fix] Multimap: Fixes for email filters - * [Fix] Multiple fixes for fann module - * [Fix] Multiple memory corruption fixes - * [Fix] Normalize path in HTTP router - * [Fix] Plug memory leak - * [Fix] Plug memory leak in adding radix trees - * [Fix] Plug memory leak in configuration parser - * [Fix] Plug memory leak in expressions parsing during reload - * [Fix] Plug memory leak in learning fuzzy storage - * [Fix] Plug memory leak in lua_tcp - * [Fix] Plug reload leaks - * [Fix] Plug termination memory leaks - * [Fix] Really increase lock lifetime - * [Fix] Replies module: fix symbol weight - * [Fix] Restore content type params related functions - * [Fix] Set task's subject from mime subject - * [Fix] Sigh, one more reload leak - * [Fix] Simplify images shingles - * [Fix] Some more memory issues are fixed - * [Fix] Stop hardcoding of lua in C - * [Fix] Stop processing of bad parts as text parts - * [Fix] Strictly filter bad characters when emittin json - * [Fix] Strings returned from lua are ephemeral - * [Fix] Support unix sockets for lua redis - * [Fix] Try to fix issues with reloading config - * [Fix] Try to fix race condition in redis_pool - * [Fix] Use checksum to avoid intersection between different ANNs - * [Fix] Use rspamd hashes in embedded ucl - * [Fix] Use sane default rewrite subject (*** SPAM *** %s) - * [Fix] Various collection mode fixes - * [Fix] Various fixes to mime parser - * [Fix] Various reload leak fixing - * [Fix] Whitelist certain extensions from archive checks - * [Rework] Add preliminary implementation of the mime parser - * [Rework] Adopt code for the new options - * [Rework] Change logger setup interface - * [Rework] Composite configuration (#1270) - * [Rework] Finally remove gmime dependency from Rspamd - * [Rework] Further fixes to symbols frequencies - * [Rework] Implement content type parser for mime - * [Rework] Kill all InternetAddressList usages - * [Rework] Multiple fixes for symbols cache statistics - * [Rework] Refactor struct names - * [Rework] Rework images fuzzy hashes algorithm - * [Rework] Rework lua_tcp to allow TCP dialog - * [Rework] Start massive rework to get rid of gmime - * [Rework] Start new approach for multiparts parsing - * [Rework] Start rework of mime addresses - * [Rework] Start rework of symbols cache updates - * [Rework] Start switching to libicu - * [Rework] Use a special structure for stats tokens - * [Rework] Use hash tables for symbols options - * [Rework] Use libicu instead of iconv for conversions - * [Rework] Use new scheme to parse mime parts - * [WebUI] Add Access-Control-Allow-Origin for cluster management - * [WebUI] Add Throughput graph autorefreshing (#820) - * [WebUI] Add Visibility.js library - * [WebUI] Add basic cluster support to Throughput tab - * [WebUI] Add graph legend entries for new DSes - * [WebUI] Add graph tab - * [WebUI] Add neighbours RRD data consolidation - * [WebUI] Add preliminary save symbols clustering - * [WebUI] Add server selector to navbar - * [WebUI] Add soft reject to auth stats - * [WebUI] Add summary to the Throughput tab - * [WebUI] Allow to save maps on the cluster - * [WebUI] Avoid extra graph redraw and alerts glitching - * [WebUI] Be more generous with AJAX timeout - * [WebUI] Disable error ring loading in `read only` mode - * [WebUI] Enclose table header cells with `tr`s - * [WebUI] Finish interface rework - * [WebUI] Fix RRD summary pie chart position - * [WebUI] Fix `All SERVERS` graph fot just one available server - * [WebUI] Fix case when no cluster is defined - * [WebUI] Fix compatibility with non-ES6 compliant browsers - * [WebUI] Fix config ID - * [WebUI] Fix configuration page partially - * [WebUI] Fix disabled state - * [WebUI] Fix graph dataset selector initialization - * [WebUI] Fix graph selectors state resetting - * [WebUI] Fix mouse events on throughput summary table area - * [WebUI] Fix multiple JS issues - * [WebUI] Fix pie chart displaying - * [WebUI] Fix read only - * [WebUI] Fix read only2 - * [WebUI] Fix retarded datatables - * [WebUI] Fix soft reject in pie chart - * [WebUI] Fix stat widgets timers multiplication on `Refresh` click - * [WebUI] Fix symbols config - * [WebUI] Fix various errors with login form - * [WebUI] Further fixes - * [WebUI] Hide learning tab in read-only mode - * [WebUI] Initial clusters support - * [WebUI] Make legend entry colours more contrast - * [WebUI] Move configuration tab to a separate module - * [WebUI] Move history tab - * [WebUI] Move symbols config as well - * [WebUI] New sec to time function - * [WebUI] Prevent multiple clicks on `Refresh` - * [WebUI] RRD summary: Hide inner labels of tiny pie sectors - * [WebUI] RRD summary: Respect undefined values - * [WebUI] Reduce font size of graph's legend - * [WebUI] Remove orphaned font duplicates - * [WebUI] Remove unused code - * [WebUI] Replace spinner with animated glyphicon - * [WebUI] Reset refresh timer on server switching - * [WebUI] Rework interface to use requirejs - * [WebUI] Rework neighbours query function - * [WebUI] Separate attributes by space - * [WebUI] Set focus to password field (#1230) - * [WebUI] Simplify neighbours table populating - * [WebUI] Start rework of modules - * [WebUI] Stop stats refreshing if the page is hidden - * [WebUI] Turn d3pie's stuff into a reusable function, - * [WebUI] Unify send data functions - * [WebUI] Update D3Evolution to 0.0.1 - * [WebUI] Update d3.js - * [WebUI] Update datatables to work with the requirejs - * [WebUI] Use unified tab click event handler, - * [WebUI] clusters for the chart - * [WebUI] fix uptime + * [Conf] Add configurations for asn, clickhouse and dcc + * [Conf] Add default config for url redirector plugin + * [Conf] Add the default config for greylist module + * [Conf] Allow to edit all local maps from WebUI by default + * [CritFix] Deal with absent headers in DKIM + * [CritFix] Do not trust remote shingles count + * [CritFix] Fix bad memory leak in TLS certificates validation + * [CritFix] Fix critical memory issues with radix maps + * [CritFix] Fix descriptors leak on reload + * [CritFix] Fix headers selection in DKIM verification + * [CritFix] Fix parsing of boundaries that end with `--` + * [CritFix] Repair PTR_ARRAY_FOREACH macro + * [Feature] Add CORS support to the controller + * [Feature] Add FROM_NAME_EXCESS_SPACE rule + * [Feature] Add REPLYTO_EMAIL_HAS_TITLE rule + * [Feature] Add `caseless_hash` method to `lua_util` + * [Feature] Add `rip` keyword to ratelimit module + * [Feature] Add a simple benchmark for content type parsing + * [Feature] Add boundaries parsing in content type + * [Feature] Add charset detection for text parts + * [Feature] Add content disposition parser + * [Feature] Add fallback if too many updates are failing + * [Feature] Add function to convert struct tm to time using timezone + * [Feature] Add function to normalize HTTP paths + * [Feature] Add fuzzy collection plugin + * [Feature] Add fuzzy logic for images + * [Feature] Add gmime parser to mime_tool + * [Feature] Add heuristic to detect broken messages + * [Feature] Add heuristic to find displayed URLs + * [Feature] Add heuristic to process broken email addresses + * [Feature] Add images normalization + * [Feature] Add mechanism for disabling composites (Fixes #1270) + * [Feature] Add method to create regexp from a glob pattern + * [Feature] Add mime encoding manipulation routines + * [Feature] Add mime tool to explore messages + * [Feature] Add more meta tokens from received headers + * [Feature] Add neighbours option to support Rspamd cluster in WebUI + * [Feature] Add new function to parse mime addresses + * [Feature] Add new methods for lua_tcp + * [Feature] Add own headers decoding routine + * [Feature] Add own routine to generate a message id + * [Feature] Add parser for SMTP date + * [Feature] Add per-task lua cache to reuse 'heavy' objects + * [Feature] Add plugins list path in WebUI + * [Feature] Add preliminary multipart support + * [Feature] Add preliminary version of DKIM signing module + * [Feature] Add profiling support in client output + * [Feature] Add rfc2047 grammar + * [Feature] Add rfc2047 variant for QP decoding + * [Feature] Add rmilter_headers module (Fixes #1227) + * [Feature] Add sse42 version of base64 decoding + * [Feature] Add ssse3 and avx2 base64 decoders + * [Feature] Add support of libgd + * [Feature] Add the preliminary version of redirects resolver in Lua + * [Feature] Add ucl_object_iterate_full function + * [Feature] Add url encoding function + * [Feature] Allow SOA requests in lua dns + * [Feature] Allow custom parse types in lua ucl + * [Feature] Allow plugins to register webui handlers + * [Feature] Allow to add options explicitly to symbols + * [Feature] Allow to call a callback when symbol frequency is on peak + * [Feature] Allow to call redirector script from SURBL + * [Feature] Allow to create variable length dkim keys + * [Feature] Allow to have module specific options for Redis in plugins + * [Feature] Allow to pass sign key directly from Lua + * [Feature] Allow to register configuration docs from Lua API + * [Feature] Allow to return options as a table + * [Feature] Allow to set peak callbacks from Lua + * [Feature] Allow to specify custom method for a message + * [Feature] Allow to store dkim keys in Redis + * [Feature] Allow to store messages in files + * [Feature] Apply DCT using AAN for fuzzy signature + * [Feature] Avira SAVAPI support + * [Feature] Cache and simplify DCT and jpeg decode + * [Feature] Cache libicu converters + * [Feature] Detect URLs with suspicious omographs + * [Feature] Do not increase score for duplicate options + * [Feature] Do not trust CTE, check base64 and qp strictly + * [Feature] Dynamic reputation in URL reputation plugin + * [Feature] Extend redis lock when learning spawned + * [Feature] Filter non-utf chars from all decoded headers + * [Feature] Fix phishing detection for IDNA urls + * [Feature] Ignore bad symbols on base64 decoding + * [Feature] Ignore too wide elements in SPF + * [Feature] Implement fuzzy collection mode + * [Feature] Implement helo maps in multimap + * [Feature] Implement human readable buckets configuration + * [Feature] Implement min-hash shingles for DCT data from images + * [Feature] Implement new algorithm for fuzzy hashes of images + * [Feature] Implement new unicode normalizer + * [Feature] Implement quoted printable decoding + * [Feature] Implement received headers flags + * [Feature] Implement rspamdgrep tool + * [Feature] Implement sane checksum for config file + * [Feature] Implement url tags concept + * [Feature] Improve detection of omographs using libicu + * [Feature] Improve url redirector module + * [Feature] Multimap: Received header processing + * [Feature] Multiple improvements in the maps + * [Feature] New URL filters in multimap + * [Feature] Plugin to force actions on selected symbols + * [Feature] RBL module: support hashing for emails and helo RBL + * [Feature] Reuse URL tags in SURBL module + * [Feature] Rework RRD ds count, add conversion path + * [Feature] Rework surbl module to avoid extra redirector calls + * [Feature] Send config id to the WebUI + * [Feature] Simplify HTTPCrypt client support + * [Feature] Skip processing for large images + * [Feature] Start collection only mode implementation for fuzzy storage + * [Feature] Start import of the optimized base64 decode + * [Feature] Store all received headers in lua + * [Feature] Store relational order of all headers in a message + * [Feature] Support DKIM signing in Lua plugins + * [Feature] Support HTTPCrypt client in lua_http + * [Feature] Support setting SMTP message in multimap + * [Feature] Support setting metric subject from Lua + * [Feature] Support setting subject in force actions module + * [Feature] Treat v6 mapped addresses as v4 addresses + * [Feature] URL reputation plugin + * [Feature] Use Redis instead of memcached in URLs redirector + * [Feature] Use Rspamd rfc2047 decoder instead of gmime one + * [Feature] Use a different normalization for fuzzy images + * [Feature] Use normalized images in fuzzy hashes + * [Feature] Use own code for parsing of date + * [Feature] Use shingles for images fuzzying + * [Feature] Use t1ha for hashes, allow inlining + * [Feature] Use t1ha instead of metrohash and xxhash32 + * [Feature] Various new features in metadata exporter module + * [Feature] rmilter_headers: authentication-results (#78) + * [Fix] Add additional check to mark redis connection inactive + * [Fix] Add packed attribute for protocol structure + * [Fix] Adopt OMOGRAPH_URL rule + * [Fix] Allow static maps + * [Fix] Allow to disable classifiers checks using settings and conditions + * [Fix] Another try to fix 0 length maps + * [Fix] Another try to fix corruption during maps reload + * [Fix] Another try to fix descriptors leak + * [Fix] Another try to fix reload and logger + * [Fix] Antivirus module: register virtual symbols for patterns + * [Fix] Avoid extensive reallocs + * [Fix] Avoid mempool leak in SA plugin on reload + * [Fix] Avoid race condition on saving cache and reload + * [Fix] Avoid reusing g_error (Fixes #1262) + * [Fix] Break pool connection on fatal redis errors + * [Fix] Check for NaN properly + * [Fix] Couple of fixes for date parsing + * [Fix] Date header timezone adjustments (#1279) + * [Fix] Deal with EOF properly + * [Fix] Decode filename in content disposition + * [Fix] Disable fuzzy images by default + * [Fix] Disable zero-copy mode for text parts to avoid crashes + * [Fix] Do not destroy session when not all finish scripts are done + * [Fix] Do not greyscale images + * [Fix] Do not leave parent-less workers processes on fatal errors + * [Fix] Do not lowercase Content-Disposition to perform decoding + * [Fix] Do not penalize characters just after numeric prefix + * [Fix] Do not refork workers that are intended to die + * [Fix] Do not set pre-result and update records for no `Queue-ID` messages + * [Fix] Do not skip post-filters when pre-filters have set some results + * [Fix] Do not stop symbols planning if async events are pending + * [Fix] Do not try to set keys for unencrypted requests in proxy + * [Fix] Encode URLs according to rfc3986 + * [Fix] Encode URLs before sending them to the protocol + * [Fix] Filter bad characters from message id + * [Fix] Fix CTE detection heuristic + * [Fix] Fix Content-Type in HTTP requests + * [Fix] Fix IDN eslds phishing checks + * [Fix] Fix adding maps from config in Lua + * [Fix] Fix another reload memory issue + * [Fix] Fix argument returned on redis backend errors + * [Fix] Fix assertion in graph handling + * [Fix] Fix body trie matching + * [Fix] Fix build + * [Fix] Fix byte array expansion during toutf8 conversion + * [Fix] Fix charset normalisation + * [Fix] Fix checking of DKIM bodies that needs just `\n` to be added + * [Fix] Fix couple of cornercases with email addresses + * [Fix] Fix couple of issues + * [Fix] Fix dependencies tracking for callback symbols + * [Fix] Fix detection of jpeg size + * [Fix] Fix errors handling in fuzzy backend initialization + * [Fix] Fix fuzzy hashes count + * [Fix] Fix globbing and convert lists to arrays in fuzzy_check + * [Fix] Fix heuristical CTE detection for QP encoding + * [Fix] Fix ignoring of bad text parts + * [Fix] Fix indexes in array access, interleave loop + * [Fix] Fix int64 -> double conversion + * [Fix] Fix invalid memory access on reload + * [Fix] Fix issues with empty updates + * [Fix] Fix issues with quoted-printable encoding + * [Fix] Fix keys names + * [Fix] Fix lots of issues in mime parser code + * [Fix] Fix lua maps load + * [Fix] Fix macro name + * [Fix] Fix mas group score calculations + * [Fix] Fix matching of the same patterns from different tries + * [Fix] Fix memory corruprtion and leak + * [Fix] Fix memory leak in HTTP maps + * [Fix] Fix memory leak in expression destroying + * [Fix] Fix memory leak in parsing of mime names + * [Fix] Fix memory leak in safe ucl iterators + * [Fix] Fix memory leak on reload in plugins + * [Fix] Fix modules reconfigure on reload + * [Fix] Fix monitored setup fro URLBLs with IP addresses + * [Fix] Fix name of var + * [Fix] Fix new rrd updates + * [Fix] Fix out of bounds access + * [Fix] Fix parsing messages with no body + * [Fix] Fix parsing of '=' character in headers + * [Fix] Fix parsing of messages with no content type + * [Fix] Fix plugins callbacks in webui + * [Fix] Fix possible memory corruption in redis pool + * [Fix] Fix probability calculations for fuzzy redis backend + * [Fix] Fix processing errors in lua_tcp + * [Fix] Fix processing of emails with name only + * [Fix] Fix processing of non-multipart messages + * [Fix] Fix processing of parts with no valid content type + * [Fix] Fix race condition in SIGUSR2 handler + * [Fix] Fix redis options parsing when no redis servers are defined + * [Fix] Fix reload and hyperscan ready event + * [Fix] Fix reload memory issue + * [Fix] Fix rra_ptr conversion + * [Fix] Fix rrd file conversion + * [Fix] Fix setting of content-type attributes + * [Fix] Fix signing headers creation in DKIM + * [Fix] Fix stddev calculations + * [Fix] Fix surbl plugin to work with composite maps + * [Fix] Fix timezones parsing + * [Fix] Fix tokens usage + * [Fix] Fix urls and emails hashes + * [Fix] Fix usage of unsafe ucl iterators + * [Fix] Fix work with broken utf8 tokens + * [Fix] Fix writing of user to roll history + * [Fix] Forgotten worker + * [Fix] Further memory leaks fixes + * [Fix] Ignore lua metatokens in bayes for now + * [Fix] Improve OMOGRAPH_URL rule + * [Fix] Lua IP from string should be invalid if parsing failed + * [Fix] Miltiple fixes to new lua_tcp, add debugging + * [Fix] More fixes for iterators cleanup + * [Fix] More fixes to logger initialization + * [Fix] More heuristic fixes for phishing detection + * [Fix] More leaks eliminated + * [Fix] More leaks... + * [Fix] More random fixes for reload... + * [Fix] Multimap: Fixes for email filters + * [Fix] Multiple fixes for fann module + * [Fix] Multiple memory corruption fixes + * [Fix] Normalize path in HTTP router + * [Fix] Plug memory leak + * [Fix] Plug memory leak in adding radix trees + * [Fix] Plug memory leak in configuration parser + * [Fix] Plug memory leak in expressions parsing during reload + * [Fix] Plug memory leak in learning fuzzy storage + * [Fix] Plug memory leak in lua_tcp + * [Fix] Plug reload leaks + * [Fix] Plug termination memory leaks + * [Fix] Really increase lock lifetime + * [Fix] Replies module: fix symbol weight + * [Fix] Restore content type params related functions + * [Fix] Set task's subject from mime subject + * [Fix] Sigh, one more reload leak + * [Fix] Simplify images shingles + * [Fix] Some more memory issues are fixed + * [Fix] Stop hardcoding of lua in C + * [Fix] Stop processing of bad parts as text parts + * [Fix] Strictly filter bad characters when emittin json + * [Fix] Strings returned from lua are ephemeral + * [Fix] Support unix sockets for lua redis + * [Fix] Try to fix issues with reloading config + * [Fix] Try to fix race condition in redis_pool + * [Fix] Use checksum to avoid intersection between different ANNs + * [Fix] Use rspamd hashes in embedded ucl + * [Fix] Use sane default rewrite subject (*** SPAM *** %s) + * [Fix] Various collection mode fixes + * [Fix] Various fixes to mime parser + * [Fix] Various reload leak fixing + * [Fix] Whitelist certain extensions from archive checks + * [Rework] Add preliminary implementation of the mime parser + * [Rework] Adopt code for the new options + * [Rework] Change logger setup interface + * [Rework] Composite configuration (#1270) + * [Rework] Finally remove gmime dependency from Rspamd + * [Rework] Further fixes to symbols frequencies + * [Rework] Implement content type parser for mime + * [Rework] Kill all InternetAddressList usages + * [Rework] Multiple fixes for symbols cache statistics + * [Rework] Refactor struct names + * [Rework] Rework images fuzzy hashes algorithm + * [Rework] Rework lua_tcp to allow TCP dialog + * [Rework] Start massive rework to get rid of gmime + * [Rework] Start new approach for multiparts parsing + * [Rework] Start rework of mime addresses + * [Rework] Start rework of symbols cache updates + * [Rework] Start switching to libicu + * [Rework] Use a special structure for stats tokens + * [Rework] Use hash tables for symbols options + * [Rework] Use libicu instead of iconv for conversions + * [Rework] Use new scheme to parse mime parts + * [WebUI] Add Access-Control-Allow-Origin for cluster management + * [WebUI] Add Throughput graph autorefreshing (#820) + * [WebUI] Add Visibility.js library + * [WebUI] Add basic cluster support to Throughput tab + * [WebUI] Add graph legend entries for new DSes + * [WebUI] Add graph tab + * [WebUI] Add neighbours RRD data consolidation + * [WebUI] Add preliminary save symbols clustering + * [WebUI] Add server selector to navbar + * [WebUI] Add soft reject to auth stats + * [WebUI] Add summary to the Throughput tab + * [WebUI] Allow to save maps on the cluster + * [WebUI] Avoid extra graph redraw and alerts glitching + * [WebUI] Be more generous with AJAX timeout + * [WebUI] Disable error ring loading in `read only` mode + * [WebUI] Enclose table header cells with `tr`s + * [WebUI] Finish interface rework + * [WebUI] Fix RRD summary pie chart position + * [WebUI] Fix `All SERVERS` graph fot just one available server + * [WebUI] Fix case when no cluster is defined + * [WebUI] Fix compatibility with non-ES6 compliant browsers + * [WebUI] Fix config ID + * [WebUI] Fix configuration page partially + * [WebUI] Fix disabled state + * [WebUI] Fix graph dataset selector initialization + * [WebUI] Fix graph selectors state resetting + * [WebUI] Fix mouse events on throughput summary table area + * [WebUI] Fix multiple JS issues + * [WebUI] Fix pie chart displaying + * [WebUI] Fix read only + * [WebUI] Fix read only2 + * [WebUI] Fix retarded datatables + * [WebUI] Fix soft reject in pie chart + * [WebUI] Fix stat widgets timers multiplication on `Refresh` click + * [WebUI] Fix symbols config + * [WebUI] Fix various errors with login form + * [WebUI] Further fixes + * [WebUI] Hide learning tab in read-only mode + * [WebUI] Initial clusters support + * [WebUI] Make legend entry colours more contrast + * [WebUI] Move configuration tab to a separate module + * [WebUI] Move history tab + * [WebUI] Move symbols config as well + * [WebUI] New sec to time function + * [WebUI] Prevent multiple clicks on `Refresh` + * [WebUI] RRD summary: Hide inner labels of tiny pie sectors + * [WebUI] RRD summary: Respect undefined values + * [WebUI] Reduce font size of graph's legend + * [WebUI] Remove orphaned font duplicates + * [WebUI] Remove unused code + * [WebUI] Replace spinner with animated glyphicon + * [WebUI] Reset refresh timer on server switching + * [WebUI] Rework interface to use requirejs + * [WebUI] Rework neighbours query function + * [WebUI] Separate attributes by space + * [WebUI] Set focus to password field (#1230) + * [WebUI] Simplify neighbours table populating + * [WebUI] Start rework of modules + * [WebUI] Stop stats refreshing if the page is hidden + * [WebUI] Turn d3pie's stuff into a reusable function, + * [WebUI] Unify send data functions + * [WebUI] Update D3Evolution to 0.0.1 + * [WebUI] Update d3.js + * [WebUI] Update datatables to work with the requirejs + * [WebUI] Use unified tab click event handler, + * [WebUI] clusters for the chart + * [WebUI] fix uptime 1.4.2: - * [CritFix] Deal with absent headers in DKIM - * [CritFix] Do not trust remote shingles count - * [CritFix] Fix headers selection in DKIM verification - * [Feature] Add EXT_CSS rule - * [Feature] Add toggle for disabling SURBLs - * [Feature] Extend redis lock when learning spawned - * [Feature] Parse <link> HTML tags - * [Fix] Avoid reusing g_error (Fixes #1262) - * [Fix] Do not reset loaded ANN when learning is requested - * [Fix] Fix another issue with external deps in SA - * [Fix] Fix body trie matching - * [Fix] Fix checking of DKIM bodies that needs just `\n` to be added - * [Fix] Fix fuzzy hashes count - * [Fix] Fix keys names - * [Fix] Fix length calculations for url encoded urls - * [Fix] Fix matching of the same patterns from different tries - * [Fix] Fix name of var - * [Fix] Fix parsing of URLs with spaces and other bad chars - * [Fix] Fix probability calculations for fuzzy redis backend - * [Fix] Fix signing headers creation in DKIM - * [Fix] Plug memory leak - * [Fix] Really fix chained SA dependencies - * [Fix] Really increase lock lifetime - * [Fix] Use checksum to avoid intersection between different ANNs - * [Fix] Use rspamd hashes in embedded ucl - * [Fix] Yet another change for testing external deps + * [CritFix] Deal with absent headers in DKIM + * [CritFix] Do not trust remote shingles count + * [CritFix] Fix headers selection in DKIM verification + * [Feature] Add EXT_CSS rule + * [Feature] Add toggle for disabling SURBLs + * [Feature] Extend redis lock when learning spawned + * [Feature] Parse <link> HTML tags + * [Fix] Avoid reusing g_error (Fixes #1262) + * [Fix] Do not reset loaded ANN when learning is requested + * [Fix] Fix another issue with external deps in SA + * [Fix] Fix body trie matching + * [Fix] Fix checking of DKIM bodies that needs just `\n` to be added + * [Fix] Fix fuzzy hashes count + * [Fix] Fix keys names + * [Fix] Fix length calculations for url encoded urls + * [Fix] Fix matching of the same patterns from different tries + * [Fix] Fix name of var + * [Fix] Fix parsing of URLs with spaces and other bad chars + * [Fix] Fix probability calculations for fuzzy redis backend + * [Fix] Fix signing headers creation in DKIM + * [Fix] Plug memory leak + * [Fix] Really fix chained SA dependencies + * [Fix] Really increase lock lifetime + * [Fix] Use checksum to avoid intersection between different ANNs + * [Fix] Use rspamd hashes in embedded ucl + * [Fix] Yet another change for testing external deps 1.4.1: - * [Feature] ASN support in Clickhouse module - * [Feature] Add clickhouse plugin - * [Feature] Add generic tool to add universal maps for lua modules - * [Feature] Add logger.debugm to debug lua modules - * [Feature] Allow to register metrics symbols using register_symbol - * [Feature] Allow to specify prefix for fann_redis - * [Feature] Clickhouse: support different masks for IPv4/IPv6 - * [Feature] Support forcing action in antivirus plugin - * [Fix] Add handling of regexp maps - * [Fix] Allow backslashes in http urls - * [Fix] Avoid mapping of empty files - * [Fix] Do not load tld file to speed up rspamadm - * [Fix] Do not resolve numeric IP addresses due to ipv6 insanity - * [Fix] Filter incorrect training data - * [Fix] Fix Fuzzyconvert tool when password or DB is given - * [Fix] Fix build with custom glib/gmime - * [Fix] Fix converting of learn count from sqlite to redis - * [Fix] Fix crashes with invalid received and task:set_from_ip - * [Fix] Fix external dependencies for SA module - * [Fix] Fix fann_redis when number of scores has been changed - * [Fix] Fix hyperscan usage for non compatible platforms - * [Fix] Fix loading of maps from UCL objects - * [Fix] Fix memory leak for task-less redis requests - * [Fix] Fix mid module with new maps syntax - * [Fix] Fix parsing of URLs with username - * [Fix] Fix re cache initialisation - * [Fix] Fix replacements to sanitize '%' character - * [Fix] Fix set and regexp like static maps - * [Fix] Fix some issues in redis settings - * [Fix] Fix static IP maps - * [Fix] Fix total learns counter for redis stats - * [Fix] Fix usage of config during reload - * [Fix] Fix various warnings and issues - * [Fix] Invalidate ANN if training data is incorrect - * [Fix] Miltiple fixes to fann_redis module - * [Fix] More fixes for URLs with backslashes - * [Fix] Properly get options for ip_score module - * [Fix] Relax requirements for Received as gmail cannot RFC - * [Fix] Remove or fix hyperscan incompatible regexps - * [Fix] Settings: correctly read redis config - * [Rework] Rework lua logger interface slightly - * [Rework] Use new maps add function + * [Feature] ASN support in Clickhouse module + * [Feature] Add clickhouse plugin + * [Feature] Add generic tool to add universal maps for lua modules + * [Feature] Add logger.debugm to debug lua modules + * [Feature] Allow to register metrics symbols using register_symbol + * [Feature] Allow to specify prefix for fann_redis + * [Feature] Clickhouse: support different masks for IPv4/IPv6 + * [Feature] Support forcing action in antivirus plugin + * [Fix] Add handling of regexp maps + * [Fix] Allow backslashes in http urls + * [Fix] Avoid mapping of empty files + * [Fix] Do not load tld file to speed up rspamadm + * [Fix] Do not resolve numeric IP addresses due to ipv6 insanity + * [Fix] Filter incorrect training data + * [Fix] Fix Fuzzyconvert tool when password or DB is given + * [Fix] Fix build with custom glib/gmime + * [Fix] Fix converting of learn count from sqlite to redis + * [Fix] Fix crashes with invalid received and task:set_from_ip + * [Fix] Fix external dependencies for SA module + * [Fix] Fix fann_redis when number of scores has been changed + * [Fix] Fix hyperscan usage for non compatible platforms + * [Fix] Fix loading of maps from UCL objects + * [Fix] Fix memory leak for task-less redis requests + * [Fix] Fix mid module with new maps syntax + * [Fix] Fix parsing of URLs with username + * [Fix] Fix re cache initialisation + * [Fix] Fix replacements to sanitize '%' character + * [Fix] Fix set and regexp like static maps + * [Fix] Fix some issues in redis settings + * [Fix] Fix static IP maps + * [Fix] Fix total learns counter for redis stats + * [Fix] Fix usage of config during reload + * [Fix] Fix various warnings and issues + * [Fix] Invalidate ANN if training data is incorrect + * [Fix] Miltiple fixes to fann_redis module + * [Fix] More fixes for URLs with backslashes + * [Fix] Properly get options for ip_score module + * [Fix] Relax requirements for Received as gmail cannot RFC + * [Fix] Remove or fix hyperscan incompatible regexps + * [Fix] Settings: correctly read redis config + * [Rework] Rework lua logger interface slightly + * [Rework] Use new maps add function 1.4.0: - * [CritFix] Add guards for inactive redis connections - * [CritFix] Another fix for proxying files using rspamd_proxy - * [CritFix] Cleanup inactive redis connections - * [CritFix] Do not sometimes try to exec posfilters before classification - * [CritFix] Fix application of IPv6 mask - * [CritFix] Fix chunked encoding when reading messages - * [CritFix] Fix file mode for rspamd_proxy - * [CritFix] Fix hyperscan compilation on regexp change - * [CritFix] Fix issue with finding of end of lines pointers - * [CritFix] Fix iteration over headers array (introduced in 1.4) - * [CritFix] Fix processing of learned tokens count for redis backend - * [CritFix] Fix race condition in checking of cached maps - * [CritFix] Fix workers scripts by sharing workers configs - * [CritFix] Introduce raw content to text parts - * [CritFix] Plug memory leak and potential memory corruption - * [Feature] Adaptive ratelimits - * [Feature] Add ASN -> rbldnsd script for asn.rspamd.com - * [Feature] Add DMARC_NA symbol - * [Feature] Add F-Prot support to antivirus module - * [Feature] Add HTTP backend to metadata exporter - * [Feature] Add Lua API module for monitored objects - * [Feature] Add R_DKIM_NA / R_SPF_NA / AUTH_NA symbols - * [Feature] Add R_DKIM_PERMFAIL symbol - * [Feature] Add R_SPF_PERMFAIL symbol - * [Feature] Add Sophos antivirus support - * [Feature] Add ZSTD compression to Lua API - * [Feature] Add `mid` Lua module - * [Feature] Add `one_param` flag for metric symbols - * [Feature] Add a generic lua classifier - * [Feature] Add a very basic interface to access workers data from on_load - * [Feature] Add ability to delete a hash by its data to fuzzy_check plugin - * [Feature] Add ability to enable/disable symbols via dynamic_conf - * [Feature] Add ability to lookup settings by key - * [Feature] Add common way to disable Lua modules - * [Feature] Add compression support to rspamd client - * [Feature] Add condition to do antiviral check - * [Feature] Add configuration for lua classifiers - * [Feature] Add configuration knobs for the errors circular buffer - * [Feature] Add decompression support in rspamd client - * [Feature] Add errors exporter to the controller - * [Feature] Add expected value for monitored DNS resources - * [Feature] Add exporter from error ringbuf to ucl - * [Feature] Add extended version for fann creation function - * [Feature] Add ffi friendly version of process_regexp function - * [Feature] Add frequency and time display to webui - * [Feature] Add fuzzy_delhash command to rspamc client - * [Feature] Add implementation of redis connections pool - * [Feature] Add latency and offline time monitoring - * [Feature] Add learning support for lua classifiers - * [Feature] Add max-size and timeout options to CGP helper - * [Feature] Add method to enable/disable symbols in config - * [Feature] Add methods to get metric's actions and symbols from Lua - * [Feature] Add mmap support to lua_text - * [Feature] Add monitored object for surbl plugin - * [Feature] Add more exceptions to surbl whitelist - * [Feature] Add more meta-tokens to bayes - * [Feature] Add neural net classifier to fann_scores module - * [Feature] Add neural net serialization/deserialization - * [Feature] Add new dynamic conf module - * [Feature] Add periodic events support for lua_config - * [Feature] Add plugin to check MX'es for the sender's domain - * [Feature] Add preliminary monitored module - * [Feature] Add preliminary support of dynamic conf updates in Redis - * [Feature] Add preliminary version of clamav plugin - * [Feature] Add redis cache to asn module - * [Feature] Add replies compression - * [Feature] Add spamhaus DROP dnsbl - * [Feature] Add support for dictionary in client compression - * [Feature] Add support for fuzzy learn and unlearn from lua - * [Feature] Add support for input encryption - * [Feature] Add support of min_learns to neural net classifier - * [Feature] Add termination callbacks for workers - * [Feature] Add user-agent for rspamc - * [Feature] Add utility to perform classifier tests - * [Feature] Add zstd compression library - * [Feature] Allow HTTPS requests in lua_http - * [Feature] Allow conditions for pre and postfilters - * [Feature] Allow custom functions for ratelimits - * [Feature] Allow for excluding messages from AV scanning based on size - * [Feature] Allow for getting worker stats from Lua - * [Feature] Allow getting task UID from Lua - * [Feature] Allow parsing of mailbox messages from the commandline - * [Feature] Allow plugins to publish their lua API via rspamd_plugins - * [Feature] Allow to compare other systems with Rspamd - * [Feature] Allow to execute Lua scripts by controller - * [Feature] Allow to have a function to set custom greylist message - * [Feature] Allow to iterate over multiple tags - * [Feature] Allow to pass extra data from plugins to log helper - * [Feature] Allow to plan new periodics at different time - * [Feature] Allow to reset hashes - * [Feature] Allow to run rspamadm lua just as a lua interpreter - * [Feature] Allow to store settings in redis - * [Feature] Allow to update dynamic conf in Redis - * [Feature] Allow to use dictionaries for compression - * [Feature] Allow to use md5, sha1, sha256, sha384 and sha512 hashes in Lua - * [Feature] Allow whitelisting by IP for greylisting plugin - * [Feature] Antivirus: Support whitelists & pattern-matching sig names - * [Feature] Backport pack/unpack routines from Lua 5.3 - * [Feature] Check settings with equal priopities in alphabetical order - * [Feature] Compress neural net in redis - * [Feature] Consider more tags when doing WHITE_ON_WHITE rule - * [Feature] Descriptive options for DMARC failure symbols - * [Feature] Descriptive options for RBL symbols - * [Feature] Enable configuration for monitored objects - * [Feature] Execute on_load scripts with ev_base ready - * [Feature] Fann scores now uses metadata from a message - * [Feature] Implement FANN threaded learning - * [Feature] Implement classifying for lua classifiers - * [Feature] Implement finish scripts for worker processes - * [Feature] Implement monitoring for DNS resources - * [Feature] Implement real priorities for pre and post filters - * [Feature] Insert two symbols: FANN_HAM and FANN_SPAM instead of one - * [Feature] Module to push metadata/messages to redis pubsub - * [Feature] Monitor RBL records - * [Feature] Move fann_classifier to a separate plugin - * [Feature] Normalize all ANN inputs - * [Feature] Preliminary version of metric exporter module - * [Feature] Preserve decompression context between tasks - * [Feature] Ratelimit: Support dynamic bucket size/leak rate - * [Feature] Relax FORGED_RECIPIENTS: allow senders to BCC themselves - * [Feature] Remove symbols weights on composites processing - * [Feature] Return symbol scores when getting resulting symbols - * [Feature] Rework lua tcp module - * [Feature] Rule to detect some obvious X-PHP-Originating-Script forgeries - * [Feature] Rule to identify some X-PHP-Script forgeries - * [Feature] Rules for scoring Google Message-ID fixes - * [Feature] Send hashes values to reply - * [Feature] Set expire for dmarc reports - * [Feature] Stop using cymru zone as it is unstable - * [Feature] Stop using of GLists for headers, improve performance - * [Feature] Store `for` in task:get_received_headers - * [Feature] Store `for` part in received headers - * [Feature] Store enabled flag for webui session - * [Feature] Store error messages in ring buffer - * [Feature] Support compressed maps - * [Feature] Support excluding selected users from ratelimits - * [Feature] Support looking up NS records in lua_dns - * [Feature] Support modern style SURBL configuration - * [Feature] Support multiple hashes in delhash path - * [Feature] Support new messages in rspamc - * [Feature] Support requests without reads in lua_tcp - * [Feature] Support setting task message from Lua - * [Feature] Track visibility of HTML elements - * [Feature] Try to add CRLF when checking DKIM - * [Feature] Try to guess line endings when folding headers - * [Feature] Try to improve normalization function for bayes - * [Feature] Use FFI to optimize SA module - * [Feature] Use length based arguments for redis, allow lua_text as arg - * [Feature] Use more layers for fann and another normalization - * [Feature] User-defined ratelimits - * [Feature] Utility to convert fuzzy storage from sqlite to redis - * [Feature] Yield DMARC_DNSFAIL on lookup failure - * [Fix] Adopt fuzzy storage for flexible backends - * [Fix] Allow plain IP addresses in Rspamd maps - * [Fix] Another fix for brain-damaged hiredis - * [Fix] Another fix for rdns write errors - * [Fix] Another fix for rdns_make_request_full invocation - * [Fix] Another fix in DKIM canonicalization - * [Fix] Another memory leak plugged - * [Fix] Another try to deal with posix idiotizm - * [Fix] Another try to fix RDNS events processing logic - * [Fix] Avoid double frees in HEAD requests - * [Fix] Avoid extra symbols for RBLs - * [Fix] Banish table.maxn from Lua parts - * [Fix] Check for socket error before connection in lua_tcp - * [Fix] Correctly propagate redis timeouts to Lua - * [Fix] Do not add extra newline in MIME mode - * [Fix] Do not be cheated by system hiredis - * [Fix] Do not classify when a message has not enough tokens - * [Fix] Do not crash on redis errors - * [Fix] Do not distinguish NXDOMAIN and NOREC for monitored - * [Fix] Do not replan retransmits if merely one server is defined - * [Fix] Do not use headers to calculate messages digests - * [Fix] Don't force action in replies module for authenticated users/local networks - * [Fix] Explicitly ban default passwords in webui - * [Fix] Finally fix ambiguity between parsed and resolved spf elts - * [Fix] Fix 'decoded' value in task:get_header_full() - * [Fix] Fix DKIM calculations - * [Fix] Fix DKIM signing for messages with no newline at the end - * [Fix] Fix DNS request in monitored - * [Fix] Fix DNS write errors processing - * [Fix] Fix HTTP methods other than GET and POST - * [Fix] Fix PERMFAIL for v6/v4 ambiguities - * [Fix] Fix absurdic scores for HFILTER_URL_ONLY - * [Fix] Fix actions in rolling history - * [Fix] Fix actrie patterns - * [Fix] Fix applying of lua dynamic confg - * [Fix] Fix autolearning errors and redis cache - * [Fix] Fix bayes learn_condition - * [Fix] Fix build with the recent OpenSSL - * [Fix] Fix caching and compressed maps - * [Fix] Fix check plain text part - * [Fix] Fix crash on OpenBSD in `url_email_start` - * [Fix] Fix double free in SPF - * [Fix] Fix extraction of shingles from redis fuzzy storage - * [Fix] Fix false sharing for symbols in the cache - * [Fix] Fix float usage in util:get_time - * [Fix] Fix folding algorithm to deal with empty tokens - * [Fix] Fix format string - * [Fix] Fix format string usage in controller errors handling - * [Fix] Fix handling of '\0' in lua_tcp - * [Fix] Fix handling of HTTP HEAD methods - * [Fix] Fix hash creation - * [Fix] Fix hiredis stupidity - * [Fix] Fix implicit settings module settingsup - * [Fix] Fix interaction with lua GC to avoid craches - * [Fix] Fix ip_score module registration - * [Fix] Fix issue with empty messages and dkim - * [Fix] Fix issues with CGP helper - * [Fix] Fix issues with the recent SPF changes - * [Fix] Fix key name to load ANN correctly - * [Fix] Fix lua tcp module by saving `do_read` in callback data - * [Fix] Fix memory leak in client when using compression - * [Fix] Fix min_learns option - * [Fix] Fix on_finish scripts and async handlers - * [Fix] Fix options for SPF dnsfail symbol - * [Fix] Fix parsing includes and redirects in SPF - * [Fix] Fix parsing of lua comments with empty lines - * [Fix] Fix parsing of unquoted HTML attributes - * [Fix] Fix periodic events and redis - * [Fix] Fix processing of fuzzy learns from Lua - * [Fix] Fix processing of redirect in SPF includes - * [Fix] Fix processing of symbols when reject limit is reached - * [Fix] Fix refcounts when map is specified by IP - * [Fix] Fix rspamd{session} class in Lua API - * [Fix] Fix setting ratelimit key for 'ip' bucket - * [Fix] Fix some cases of TLD urls detector - * [Fix] Fix statconvert tool - * [Fix] Fix stats for backend-less classifiers - * [Fix] Fix training script for fann_redis - * [Fix] Fix variable in ann module - * [Fix] Fix various errors in lua dynamic conf plugin - * [Fix] Forget old ANN when max_usages is reached to avoid overtrain - * [Fix] Further canonicalization fixes - * [Fix] Further fixes for fann_redis prefixes - * [Fix] Handle failures for inactive pooled connections - * [Fix] Improve multimap info message - * [Fix] More fixes in ANN loading - * [Fix] More fixes to fann_redis - * [Fix] More issues in fann_redis - * [Fix] More spaces fix in DKIM signature - * [Fix] Multiple fixes to asn script, add IPv6 support - * [Fix] Multiple issues in fann_redis - * [Fix] No greylist rejected messages - * [Fix] One more attempt to fix lua_redis - * [Fix] One more check for readdir... - * [Fix] Params should be treated as a hash - * [Fix] Plug memory leak in regexp desctructor - * [Fix] Process headers only once - * [Fix] Properly handle nil values in ratelimit plugin - * [Fix] Really fix redis shingles check - * [Fix] Remove fann with incorrect layers count - * [Fix] Remove mentions of deleted include - * [Fix] Remove some incompatible functions - * [Fix] Settings: fix `authenticated` parameter (#886) - * [Fix] Skip MX check for authenticated users and local networks - * [Fix] Slightly fix ANN routines - * [Fix] Stop caching records with DNS failures - * [Fix] Treat all errors in redis_pool as fatal errors for a connection - * [Fix] Try avoid false-positives in HEADER_FORGED_MDN rule - * [Fix] Try to avoid race condition when using rrd - * [Fix] Try to reload redis scripts if they are missing - * [Fix] Unbreak once_received skipping for local networks - * [Fix] Unlock ANN on error - * [Fix] Use memmove for overlapping regions - * [Fix] Use real size instead of displayed for core limits - * [Fix] Use the correct macro to get the size of control - * [Fix] Various fixes for errors ringbuffer - * [Fix] Yield R_SPF_DNSFAIL if lookup of included record fails - * [Fix] mid: fix map initialization - * [Fix] mid: handle incorrect rgexps in the map - * [Rework] Add extract training data function to fann_redis - * [Rework] Add preliminary train tests - * [Rework] Add redis storage feature to fann_redis - * [Rework] Adopt fuzzy storage for abstract backend - * [Rework] Adopt plugins - * [Rework] First reiteration on fann scores - * [Rework] Implement loading/invalidating - * [Rework] Make lua_redis task agnostic - * [Rework] Make rspamd protocol messages useful - * [Rework] Massive removal of legacy code - * [Rework] More cleanup actions - * [Rework] Remove legacy code never used for classifiers - * [Rework] Remove outdated and unused lua_session module - * [Rework] Reorganize fuzzy backend structure - * [Rework] Reorganize the internal backend structure - * [Rework] Restore old fann_scores, move common parts - * [Rework] Rework and simplify rbl plugin - * [Rework] Rework parsing of DMARC records + * [CritFix] Add guards for inactive redis connections + * [CritFix] Another fix for proxying files using rspamd_proxy + * [CritFix] Cleanup inactive redis connections + * [CritFix] Do not sometimes try to exec posfilters before classification + * [CritFix] Fix application of IPv6 mask + * [CritFix] Fix chunked encoding when reading messages + * [CritFix] Fix file mode for rspamd_proxy + * [CritFix] Fix hyperscan compilation on regexp change + * [CritFix] Fix issue with finding of end of lines pointers + * [CritFix] Fix iteration over headers array (introduced in 1.4) + * [CritFix] Fix processing of learned tokens count for redis backend + * [CritFix] Fix race condition in checking of cached maps + * [CritFix] Fix workers scripts by sharing workers configs + * [CritFix] Introduce raw content to text parts + * [CritFix] Plug memory leak and potential memory corruption + * [Feature] Adaptive ratelimits + * [Feature] Add ASN -> rbldnsd script for asn.rspamd.com + * [Feature] Add DMARC_NA symbol + * [Feature] Add F-Prot support to antivirus module + * [Feature] Add HTTP backend to metadata exporter + * [Feature] Add Lua API module for monitored objects + * [Feature] Add R_DKIM_NA / R_SPF_NA / AUTH_NA symbols + * [Feature] Add R_DKIM_PERMFAIL symbol + * [Feature] Add R_SPF_PERMFAIL symbol + * [Feature] Add Sophos antivirus support + * [Feature] Add ZSTD compression to Lua API + * [Feature] Add `mid` Lua module + * [Feature] Add `one_param` flag for metric symbols + * [Feature] Add a generic lua classifier + * [Feature] Add a very basic interface to access workers data from on_load + * [Feature] Add ability to delete a hash by its data to fuzzy_check plugin + * [Feature] Add ability to enable/disable symbols via dynamic_conf + * [Feature] Add ability to lookup settings by key + * [Feature] Add common way to disable Lua modules + * [Feature] Add compression support to rspamd client + * [Feature] Add condition to do antiviral check + * [Feature] Add configuration for lua classifiers + * [Feature] Add configuration knobs for the errors circular buffer + * [Feature] Add decompression support in rspamd client + * [Feature] Add errors exporter to the controller + * [Feature] Add expected value for monitored DNS resources + * [Feature] Add exporter from error ringbuf to ucl + * [Feature] Add extended version for fann creation function + * [Feature] Add ffi friendly version of process_regexp function + * [Feature] Add frequency and time display to webui + * [Feature] Add fuzzy_delhash command to rspamc client + * [Feature] Add implementation of redis connections pool + * [Feature] Add latency and offline time monitoring + * [Feature] Add learning support for lua classifiers + * [Feature] Add max-size and timeout options to CGP helper + * [Feature] Add method to enable/disable symbols in config + * [Feature] Add methods to get metric's actions and symbols from Lua + * [Feature] Add mmap support to lua_text + * [Feature] Add monitored object for surbl plugin + * [Feature] Add more exceptions to surbl whitelist + * [Feature] Add more meta-tokens to bayes + * [Feature] Add neural net classifier to fann_scores module + * [Feature] Add neural net serialization/deserialization + * [Feature] Add new dynamic conf module + * [Feature] Add periodic events support for lua_config + * [Feature] Add plugin to check MX'es for the sender's domain + * [Feature] Add preliminary monitored module + * [Feature] Add preliminary support of dynamic conf updates in Redis + * [Feature] Add preliminary version of clamav plugin + * [Feature] Add redis cache to asn module + * [Feature] Add replies compression + * [Feature] Add spamhaus DROP dnsbl + * [Feature] Add support for dictionary in client compression + * [Feature] Add support for fuzzy learn and unlearn from lua + * [Feature] Add support for input encryption + * [Feature] Add support of min_learns to neural net classifier + * [Feature] Add termination callbacks for workers + * [Feature] Add user-agent for rspamc + * [Feature] Add utility to perform classifier tests + * [Feature] Add zstd compression library + * [Feature] Allow HTTPS requests in lua_http + * [Feature] Allow conditions for pre and postfilters + * [Feature] Allow custom functions for ratelimits + * [Feature] Allow for excluding messages from AV scanning based on size + * [Feature] Allow for getting worker stats from Lua + * [Feature] Allow getting task UID from Lua + * [Feature] Allow parsing of mailbox messages from the commandline + * [Feature] Allow plugins to publish their lua API via rspamd_plugins + * [Feature] Allow to compare other systems with Rspamd + * [Feature] Allow to execute Lua scripts by controller + * [Feature] Allow to have a function to set custom greylist message + * [Feature] Allow to iterate over multiple tags + * [Feature] Allow to pass extra data from plugins to log helper + * [Feature] Allow to plan new periodics at different time + * [Feature] Allow to reset hashes + * [Feature] Allow to run rspamadm lua just as a lua interpreter + * [Feature] Allow to store settings in redis + * [Feature] Allow to update dynamic conf in Redis + * [Feature] Allow to use dictionaries for compression + * [Feature] Allow to use md5, sha1, sha256, sha384 and sha512 hashes in Lua + * [Feature] Allow whitelisting by IP for greylisting plugin + * [Feature] Antivirus: Support whitelists & pattern-matching sig names + * [Feature] Backport pack/unpack routines from Lua 5.3 + * [Feature] Check settings with equal priopities in alphabetical order + * [Feature] Compress neural net in redis + * [Feature] Consider more tags when doing WHITE_ON_WHITE rule + * [Feature] Descriptive options for DMARC failure symbols + * [Feature] Descriptive options for RBL symbols + * [Feature] Enable configuration for monitored objects + * [Feature] Execute on_load scripts with ev_base ready + * [Feature] Fann scores now uses metadata from a message + * [Feature] Implement FANN threaded learning + * [Feature] Implement classifying for lua classifiers + * [Feature] Implement finish scripts for worker processes + * [Feature] Implement monitoring for DNS resources + * [Feature] Implement real priorities for pre and post filters + * [Feature] Insert two symbols: FANN_HAM and FANN_SPAM instead of one + * [Feature] Module to push metadata/messages to redis pubsub + * [Feature] Monitor RBL records + * [Feature] Move fann_classifier to a separate plugin + * [Feature] Normalize all ANN inputs + * [Feature] Preliminary version of metric exporter module + * [Feature] Preserve decompression context between tasks + * [Feature] Ratelimit: Support dynamic bucket size/leak rate + * [Feature] Relax FORGED_RECIPIENTS: allow senders to BCC themselves + * [Feature] Remove symbols weights on composites processing + * [Feature] Return symbol scores when getting resulting symbols + * [Feature] Rework lua tcp module + * [Feature] Rule to detect some obvious X-PHP-Originating-Script forgeries + * [Feature] Rule to identify some X-PHP-Script forgeries + * [Feature] Rules for scoring Google Message-ID fixes + * [Feature] Send hashes values to reply + * [Feature] Set expire for dmarc reports + * [Feature] Stop using cymru zone as it is unstable + * [Feature] Stop using of GLists for headers, improve performance + * [Feature] Store `for` in task:get_received_headers + * [Feature] Store `for` part in received headers + * [Feature] Store enabled flag for webui session + * [Feature] Store error messages in ring buffer + * [Feature] Support compressed maps + * [Feature] Support excluding selected users from ratelimits + * [Feature] Support looking up NS records in lua_dns + * [Feature] Support modern style SURBL configuration + * [Feature] Support multiple hashes in delhash path + * [Feature] Support new messages in rspamc + * [Feature] Support requests without reads in lua_tcp + * [Feature] Support setting task message from Lua + * [Feature] Track visibility of HTML elements + * [Feature] Try to add CRLF when checking DKIM + * [Feature] Try to guess line endings when folding headers + * [Feature] Try to improve normalization function for bayes + * [Feature] Use FFI to optimize SA module + * [Feature] Use length based arguments for redis, allow lua_text as arg + * [Feature] Use more layers for fann and another normalization + * [Feature] User-defined ratelimits + * [Feature] Utility to convert fuzzy storage from sqlite to redis + * [Feature] Yield DMARC_DNSFAIL on lookup failure + * [Fix] Adopt fuzzy storage for flexible backends + * [Fix] Allow plain IP addresses in Rspamd maps + * [Fix] Another fix for brain-damaged hiredis + * [Fix] Another fix for rdns write errors + * [Fix] Another fix for rdns_make_request_full invocation + * [Fix] Another fix in DKIM canonicalization + * [Fix] Another memory leak plugged + * [Fix] Another try to deal with posix idiotizm + * [Fix] Another try to fix RDNS events processing logic + * [Fix] Avoid double frees in HEAD requests + * [Fix] Avoid extra symbols for RBLs + * [Fix] Banish table.maxn from Lua parts + * [Fix] Check for socket error before connection in lua_tcp + * [Fix] Correctly propagate redis timeouts to Lua + * [Fix] Do not add extra newline in MIME mode + * [Fix] Do not be cheated by system hiredis + * [Fix] Do not classify when a message has not enough tokens + * [Fix] Do not crash on redis errors + * [Fix] Do not distinguish NXDOMAIN and NOREC for monitored + * [Fix] Do not replan retransmits if merely one server is defined + * [Fix] Do not use headers to calculate messages digests + * [Fix] Don't force action in replies module for authenticated users/local networks + * [Fix] Explicitly ban default passwords in webui + * [Fix] Finally fix ambiguity between parsed and resolved spf elts + * [Fix] Fix 'decoded' value in task:get_header_full() + * [Fix] Fix DKIM calculations + * [Fix] Fix DKIM signing for messages with no newline at the end + * [Fix] Fix DNS request in monitored + * [Fix] Fix DNS write errors processing + * [Fix] Fix HTTP methods other than GET and POST + * [Fix] Fix PERMFAIL for v6/v4 ambiguities + * [Fix] Fix absurdic scores for HFILTER_URL_ONLY + * [Fix] Fix actions in rolling history + * [Fix] Fix actrie patterns + * [Fix] Fix applying of lua dynamic confg + * [Fix] Fix autolearning errors and redis cache + * [Fix] Fix bayes learn_condition + * [Fix] Fix build with the recent OpenSSL + * [Fix] Fix caching and compressed maps + * [Fix] Fix check plain text part + * [Fix] Fix crash on OpenBSD in `url_email_start` + * [Fix] Fix double free in SPF + * [Fix] Fix extraction of shingles from redis fuzzy storage + * [Fix] Fix false sharing for symbols in the cache + * [Fix] Fix float usage in util:get_time + * [Fix] Fix folding algorithm to deal with empty tokens + * [Fix] Fix format string + * [Fix] Fix format string usage in controller errors handling + * [Fix] Fix handling of '\0' in lua_tcp + * [Fix] Fix handling of HTTP HEAD methods + * [Fix] Fix hash creation + * [Fix] Fix hiredis stupidity + * [Fix] Fix implicit settings module settingsup + * [Fix] Fix interaction with lua GC to avoid craches + * [Fix] Fix ip_score module registration + * [Fix] Fix issue with empty messages and dkim + * [Fix] Fix issues with CGP helper + * [Fix] Fix issues with the recent SPF changes + * [Fix] Fix key name to load ANN correctly + * [Fix] Fix lua tcp module by saving `do_read` in callback data + * [Fix] Fix memory leak in client when using compression + * [Fix] Fix min_learns option + * [Fix] Fix on_finish scripts and async handlers + * [Fix] Fix options for SPF dnsfail symbol + * [Fix] Fix parsing includes and redirects in SPF + * [Fix] Fix parsing of lua comments with empty lines + * [Fix] Fix parsing of unquoted HTML attributes + * [Fix] Fix periodic events and redis + * [Fix] Fix processing of fuzzy learns from Lua + * [Fix] Fix processing of redirect in SPF includes + * [Fix] Fix processing of symbols when reject limit is reached + * [Fix] Fix refcounts when map is specified by IP + * [Fix] Fix rspamd{session} class in Lua API + * [Fix] Fix setting ratelimit key for 'ip' bucket + * [Fix] Fix some cases of TLD urls detector + * [Fix] Fix statconvert tool + * [Fix] Fix stats for backend-less classifiers + * [Fix] Fix training script for fann_redis + * [Fix] Fix variable in ann module + * [Fix] Fix various errors in lua dynamic conf plugin + * [Fix] Forget old ANN when max_usages is reached to avoid overtrain + * [Fix] Further canonicalization fixes + * [Fix] Further fixes for fann_redis prefixes + * [Fix] Handle failures for inactive pooled connections + * [Fix] Improve multimap info message + * [Fix] More fixes in ANN loading + * [Fix] More fixes to fann_redis + * [Fix] More issues in fann_redis + * [Fix] More spaces fix in DKIM signature + * [Fix] Multiple fixes to asn script, add IPv6 support + * [Fix] Multiple issues in fann_redis + * [Fix] No greylist rejected messages + * [Fix] One more attempt to fix lua_redis + * [Fix] One more check for readdir... + * [Fix] Params should be treated as a hash + * [Fix] Plug memory leak in regexp desctructor + * [Fix] Process headers only once + * [Fix] Properly handle nil values in ratelimit plugin + * [Fix] Really fix redis shingles check + * [Fix] Remove fann with incorrect layers count + * [Fix] Remove mentions of deleted include + * [Fix] Remove some incompatible functions + * [Fix] Settings: fix `authenticated` parameter (#886) + * [Fix] Skip MX check for authenticated users and local networks + * [Fix] Slightly fix ANN routines + * [Fix] Stop caching records with DNS failures + * [Fix] Treat all errors in redis_pool as fatal errors for a connection + * [Fix] Try avoid false-positives in HEADER_FORGED_MDN rule + * [Fix] Try to avoid race condition when using rrd + * [Fix] Try to reload redis scripts if they are missing + * [Fix] Unbreak once_received skipping for local networks + * [Fix] Unlock ANN on error + * [Fix] Use memmove for overlapping regions + * [Fix] Use real size instead of displayed for core limits + * [Fix] Use the correct macro to get the size of control + * [Fix] Various fixes for errors ringbuffer + * [Fix] Yield R_SPF_DNSFAIL if lookup of included record fails + * [Fix] mid: fix map initialization + * [Fix] mid: handle incorrect rgexps in the map + * [Rework] Add extract training data function to fann_redis + * [Rework] Add preliminary train tests + * [Rework] Add redis storage feature to fann_redis + * [Rework] Adopt fuzzy storage for abstract backend + * [Rework] Adopt plugins + * [Rework] First reiteration on fann scores + * [Rework] Implement loading/invalidating + * [Rework] Make lua_redis task agnostic + * [Rework] Make rspamd protocol messages useful + * [Rework] Massive removal of legacy code + * [Rework] More cleanup actions + * [Rework] Remove legacy code never used for classifiers + * [Rework] Remove outdated and unused lua_session module + * [Rework] Reorganize fuzzy backend structure + * [Rework] Reorganize the internal backend structure + * [Rework] Restore old fann_scores, move common parts + * [Rework] Rework and simplify rbl plugin + * [Rework] Rework parsing of DMARC records 1.3.4: - * [Feature] ASN module; support matching ASN/country in multimap - * [Feature] Add SPF method in spf return result - * [Feature] Add Yandex and Mail.ru forwarding rules - * [Feature] Add mempool maps in multimap - * [Feature] Add rule for identifying mail sent by eval()'d PHP code - * [Feature] Add support of stub DNSSEC resolver to rdns - * [Feature] Add task:get_digest method - * [Feature] Allow for more fine-grained scoring for ip_score - * [Feature] Allow to get digest of a mime part from lua - * [Feature] Allow to print message digest in logs - * [Feature] Fold DKIM-Signature header - * [Feature] Implement encrypted logs - * [Feature] Log URLs encrypted if we have log encryption pubkey - * [Feature] Pass authenticated bit to lua - * [Feature] Read redis backend statistics configuration from global section - * [Feature] Show the exact value matched for multima symbols - * [Feature] Store task checksum - * [Fix] Avoid setting limits when required elements are missing - * [Fix] DMARC: Fix alignment checking for subdomains - * [Fix] DMARC: deal with missing and spurious spaces - * [Fix] Defer insertion of results in ip_score to avoid skewing stats - * [Fix] Disable DMARC for local/authorized mail - * [Fix] Fix handling of proxied headers in controller - * [Fix] Fix hex printing of strings - * [Fix] Fix issue with spaces in maps - * [Fix] Fix parsing of forwarded IP - * [Fix] Fix reload in some plugins and workers - * [Fix] Fix reloading on SIGHUP - * [Fix] Fix some border cases for DKIM canonicalization - * [Fix] Fix url maps - * [Fix] Make dnssec configurable option disabled by default for now - * [Fix] rspamadm statconvert: force db to be a string - * [Fix] rspamadm statconvert: use db/password for learn cache - * [Rework] Rework flags in rspamd logger + * [Feature] ASN module; support matching ASN/country in multimap + * [Feature] Add SPF method in spf return result + * [Feature] Add Yandex and Mail.ru forwarding rules + * [Feature] Add mempool maps in multimap + * [Feature] Add rule for identifying mail sent by eval()'d PHP code + * [Feature] Add support of stub DNSSEC resolver to rdns + * [Feature] Add task:get_digest method + * [Feature] Allow for more fine-grained scoring for ip_score + * [Feature] Allow to get digest of a mime part from lua + * [Feature] Allow to print message digest in logs + * [Feature] Fold DKIM-Signature header + * [Feature] Implement encrypted logs + * [Feature] Log URLs encrypted if we have log encryption pubkey + * [Feature] Pass authenticated bit to lua + * [Feature] Read redis backend statistics configuration from global section + * [Feature] Show the exact value matched for multima symbols + * [Feature] Store task checksum + * [Fix] Avoid setting limits when required elements are missing + * [Fix] DMARC: Fix alignment checking for subdomains + * [Fix] DMARC: deal with missing and spurious spaces + * [Fix] Defer insertion of results in ip_score to avoid skewing stats + * [Fix] Disable DMARC for local/authorized mail + * [Fix] Fix handling of proxied headers in controller + * [Fix] Fix hex printing of strings + * [Fix] Fix issue with spaces in maps + * [Fix] Fix parsing of forwarded IP + * [Fix] Fix reload in some plugins and workers + * [Fix] Fix reloading on SIGHUP + * [Fix] Fix some border cases for DKIM canonicalization + * [Fix] Fix url maps + * [Fix] Make dnssec configurable option disabled by default for now + * [Fix] rspamadm statconvert: force db to be a string + * [Fix] rspamadm statconvert: use db/password for learn cache + * [Rework] Rework flags in rspamd logger 1.3.3: - * [CritFix] Check hyperscan cache sanity before loading - * [CritFix] Fix setting of fuzzy keys (completely breaks fuzzy storage) - * [Feature] Add SARBL (sarbl.org) uribl - * [Feature] Add `--search-pattern` option to rspamd_stats - * [Feature] Add some sanity check for very long from/to log elements - * [Feature] Allow to create hashes from string in a single step - * [Feature] Fix order of pre and postfilters - * [Feature] Improve lua URLs API - * [Feature] Improve message about fuzzy rules - * [Feature] Pre-calculate blake2 digest for all parts - * [Feature] Print radix duplicate keys as IP addresses - * [Feature] Simple mechanism for disabling RBLs in local.d/rbl.conf - * [Feature] Use faster hash function for fuzzy storage - * [Feature] rspamd_stats: support log directory reading - * [Fix] Add sanity check for url filters - * [Fix] Do not show rmilter section as a fake metric in rspamc - * [Fix] Fix URL filters - * [Fix] Fix a stupid mistake in util.strequal_caseless - * [Fix] Fix blake2b hash of the string "rspamd" - * [Fix] Fix filename maps filter - * [Fix] Fix finding tld in util.get_tld - * [Fix] Fix multimap content filters - * [Fix] Fix returning boolean from Lua - * [Fix] Fix returning of REDIS_NIL - * [Fix] Try to deal with multiple workers terminated - * [Fix] Use forced DNS request when calling for lua_http - * [Rework] Rework multimap filters, add redis maps + * [CritFix] Check hyperscan cache sanity before loading + * [CritFix] Fix setting of fuzzy keys (completely breaks fuzzy storage) + * [Feature] Add SARBL (sarbl.org) uribl + * [Feature] Add `--search-pattern` option to rspamd_stats + * [Feature] Add some sanity check for very long from/to log elements + * [Feature] Allow to create hashes from string in a single step + * [Feature] Fix order of pre and postfilters + * [Feature] Improve lua URLs API + * [Feature] Improve message about fuzzy rules + * [Feature] Pre-calculate blake2 digest for all parts + * [Feature] Print radix duplicate keys as IP addresses + * [Feature] Simple mechanism for disabling RBLs in local.d/rbl.conf + * [Feature] Use faster hash function for fuzzy storage + * [Feature] rspamd_stats: support log directory reading + * [Fix] Add sanity check for url filters + * [Fix] Do not show rmilter section as a fake metric in rspamc + * [Fix] Fix URL filters + * [Fix] Fix a stupid mistake in util.strequal_caseless + * [Fix] Fix blake2b hash of the string "rspamd" + * [Fix] Fix filename maps filter + * [Fix] Fix finding tld in util.get_tld + * [Fix] Fix multimap content filters + * [Fix] Fix returning boolean from Lua + * [Fix] Fix returning of REDIS_NIL + * [Fix] Try to deal with multiple workers terminated + * [Fix] Use forced DNS request when calling for lua_http + * [Rework] Rework multimap filters, add redis maps 1.3.2: - * [Feature] Add a special symbol for SPF DNS errors: R_SPF_DNSFAIL - * [Feature] Add correlations report in fuzzy stats - * [Feature] Add experimental CGP integration - * [Feature] Add method to get urls length in a text part - * [Feature] Add new methods to lua_html to access HTML tags - * [Feature] Allow all types of symbols to be added via __newindex method - * [Feature] Allow to create settings for authenticated users - * [Feature] Allow to get block content for HTML tags - * [Feature] Improve DNS failures when dealing with SPF - * [Feature] Properly implement R_WHITE_ON_WHITE rule - * [Feature] Remove old ugly rules - * [Feature] Rspamc can now add dkim signature in mime mode - * [Feature] Store content length for HTML tags - * [Feature] Support reacher set of HTML colors - * [Feature] Try to avoid FP for low contrast fonts detection - * [Fix] Add missing HTML colors - * [Fix] Add spaces to dkim signature to allow folding - * [Fix] Avoid returning NaN as score on scan - * [Fix] Decode entitles in href parts - * [Fix] Do not cache SPF records with DNS errors - * [Fix] Do not crash on cyclic depends - * [Fix] Do not insert HELO/HOSTNAME unknown when they are not passed - * [Fix] Do not set absent hostname to "unknown" - * [Fix] Do not stress redis with KEYS command (#791) - * [Fix] Fix DMARC_BAD_POLICY symbol - * [Fix] Fix HFILTER_URL module - * [Fix] Fix HFILTER_URL_ONELINE rule - * [Fix] Fix buffering in CGP integration - * [Fix] Fix colors propagation from parent nodes - * [Fix] Fix confusing OpenSSL API usage of i2d_RSAPublicKey - * [Fix] Fix dependencies id sanity check - * [Fix] Fix folding for semicolon separated tokens - * [Fix] Fix largest possible TLD behaviour - * [Fix] Fix last token folding - * [Fix] Fix length calculations in white on white rule - * [Fix] Fix multiple request headers structure - * [Fix] Fix multiple values headers freeing - * [Fix] Fix parsing of background color - * [Fix] Fix printing from field in log_urls - * [Fix] Fix processing of last element of DMARC policies - * [Fix] Further fixes for HTML colors - * [Fix] Further fixes for multiple values headers - * [Fix] Further fixes for white on white rule - * [Fix] Further fixes in HTML tags parsing - * [Fix] Ignore content type/subtype case - * [Fix] Increase score of R_WHITE_ON_WHITE - * [Fix] Parse CGP envelope data - * [Fix] Propagate colors in HTML - * [Fix] Restore multiple values headers in protocol - * [Fix] Restore multiple values in headers processing - * [Fix] Some more changes to tag's content length calculations - * [Fix] Some more fixes for low contrast fonts detector - * [Fix] SpamAssassin plugin: support check_freemail_header('EnvelopeFrom', [..]) - * [Fix] Trigger HTML_SHORT_LINK_IMG on any external image - * [Fix] rspamd_stats: remove deprecated defined(@array) + * [Feature] Add a special symbol for SPF DNS errors: R_SPF_DNSFAIL + * [Feature] Add correlations report in fuzzy stats + * [Feature] Add experimental CGP integration + * [Feature] Add method to get urls length in a text part + * [Feature] Add new methods to lua_html to access HTML tags + * [Feature] Allow all types of symbols to be added via __newindex method + * [Feature] Allow to create settings for authenticated users + * [Feature] Allow to get block content for HTML tags + * [Feature] Improve DNS failures when dealing with SPF + * [Feature] Properly implement R_WHITE_ON_WHITE rule + * [Feature] Remove old ugly rules + * [Feature] Rspamc can now add dkim signature in mime mode + * [Feature] Store content length for HTML tags + * [Feature] Support reacher set of HTML colors + * [Feature] Try to avoid FP for low contrast fonts detection + * [Fix] Add missing HTML colors + * [Fix] Add spaces to dkim signature to allow folding + * [Fix] Avoid returning NaN as score on scan + * [Fix] Decode entitles in href parts + * [Fix] Do not cache SPF records with DNS errors + * [Fix] Do not crash on cyclic depends + * [Fix] Do not insert HELO/HOSTNAME unknown when they are not passed + * [Fix] Do not set absent hostname to "unknown" + * [Fix] Do not stress redis with KEYS command (#791) + * [Fix] Fix DMARC_BAD_POLICY symbol + * [Fix] Fix HFILTER_URL module + * [Fix] Fix HFILTER_URL_ONELINE rule + * [Fix] Fix buffering in CGP integration + * [Fix] Fix colors propagation from parent nodes + * [Fix] Fix confusing OpenSSL API usage of i2d_RSAPublicKey + * [Fix] Fix dependencies id sanity check + * [Fix] Fix folding for semicolon separated tokens + * [Fix] Fix largest possible TLD behaviour + * [Fix] Fix last token folding + * [Fix] Fix length calculations in white on white rule + * [Fix] Fix multiple request headers structure + * [Fix] Fix multiple values headers freeing + * [Fix] Fix parsing of background color + * [Fix] Fix printing from field in log_urls + * [Fix] Fix processing of last element of DMARC policies + * [Fix] Further fixes for HTML colors + * [Fix] Further fixes for multiple values headers + * [Fix] Further fixes for white on white rule + * [Fix] Further fixes in HTML tags parsing + * [Fix] Ignore content type/subtype case + * [Fix] Increase score of R_WHITE_ON_WHITE + * [Fix] Parse CGP envelope data + * [Fix] Propagate colors in HTML + * [Fix] Restore multiple values headers in protocol + * [Fix] Restore multiple values in headers processing + * [Fix] Some more changes to tag's content length calculations + * [Fix] Some more fixes for low contrast fonts detector + * [Fix] SpamAssassin plugin: support check_freemail_header('EnvelopeFrom', [..]) + * [Fix] Trigger HTML_SHORT_LINK_IMG on any external image + * [Fix] rspamd_stats: remove deprecated defined(@array) 1.3.1: - * [CritFix] Fix catena passwords validation - * [CritFix] Fix crash when the first received is faked - * [Feature] Add DMARC_BAD_POLICY symbol when DMARC policy was invalid - * [Feature] Allow for matching hostnames in multimap (#773) - * [Feature] Allow for setting action based on DMARC disposition - * [Feature] Allow limiting of the inbound message size - * [Feature] Allow maps with multiple symbols and scores - * [Feature] Allow regexps in the emails maps - * [Feature] Allow to register metric symbols from multimap - * [Feature] Allow to reset redis tokens instead of appendig values - * [Feature] Allow to store strings in radix maps - * [Feature] Check UTF validity when there are utf regexps in a map - * [Feature] Correctly work when there is no hard reject action - * [Feature] Implement dependencies for maps - * [Fix] Another effort to unbreak sqlite locking - * [Fix] Avoid crash when closing mmapped file - * [Fix] Do not break history on NaN in required score - * [Fix] Ensure that hyperscan cache written is written properly - * [Fix] Filter NaN from scores in history - * [Fix] Fix DNSBL maps - * [Fix] Fix another locking issue in sqlite - * [Fix] Fix another locking issue with mapped files - * [Fix] Fix deadlock in mmaped file stats - * [Fix] Fix dependencies in multimap plugin - * [Fix] Fix emails module configuration - * [Fix] Fix greylist plugin (#755) - * [Fix] Fix greylisting plugin variable usage - * [Fix] Fix installed permissions for rspamd_stats - * [Fix] Fix locking in mmapped statistics - * [Fix] Fix paths in tests - * [Fix] Fix prefilter mode for multimap - * [Fix] Forgot to commit leftover changes - * [Fix] Really fix local.d includes - * [Fix] Restore selective greylisting behaviour - * [Fix] Set max size on per connection basis - * [Fix] Use temporary storage for hyperscan cache - * [Rework] Remove systemd socket activation + * [CritFix] Fix catena passwords validation + * [CritFix] Fix crash when the first received is faked + * [Feature] Add DMARC_BAD_POLICY symbol when DMARC policy was invalid + * [Feature] Allow for matching hostnames in multimap (#773) + * [Feature] Allow for setting action based on DMARC disposition + * [Feature] Allow limiting of the inbound message size + * [Feature] Allow maps with multiple symbols and scores + * [Feature] Allow regexps in the emails maps + * [Feature] Allow to register metric symbols from multimap + * [Feature] Allow to reset redis tokens instead of appendig values + * [Feature] Allow to store strings in radix maps + * [Feature] Check UTF validity when there are utf regexps in a map + * [Feature] Correctly work when there is no hard reject action + * [Feature] Implement dependencies for maps + * [Fix] Another effort to unbreak sqlite locking + * [Fix] Avoid crash when closing mmapped file + * [Fix] Do not break history on NaN in required score + * [Fix] Ensure that hyperscan cache written is written properly + * [Fix] Filter NaN from scores in history + * [Fix] Fix DNSBL maps + * [Fix] Fix another locking issue in sqlite + * [Fix] Fix another locking issue with mapped files + * [Fix] Fix deadlock in mmaped file stats + * [Fix] Fix dependencies in multimap plugin + * [Fix] Fix emails module configuration + * [Fix] Fix greylist plugin (#755) + * [Fix] Fix greylisting plugin variable usage + * [Fix] Fix installed permissions for rspamd_stats + * [Fix] Fix locking in mmapped statistics + * [Fix] Fix paths in tests + * [Fix] Fix prefilter mode for multimap + * [Fix] Forgot to commit leftover changes + * [Fix] Really fix local.d includes + * [Fix] Restore selective greylisting behaviour + * [Fix] Set max size on per connection basis + * [Fix] Use temporary storage for hyperscan cache + * [Rework] Remove systemd socket activation 1.3.0: - * [CritFix] Fix SA rawbody processing - exclude top part - * [CritFix] Fix decoding of UTF HTML entitles - * [CritFix] Fix encrypted fuzzy requests - * [CritFix] Fix leak of shared memory fds and files - * [CritFix] Fix levenshtein distance calculations - * [CritFix] Fix mime headers processing - * [CritFix] Fix parsing of URLs in texts - * [CritFix] Fix parsing of missing classes - * [CritFix] Fix redis structure by adding {NULL, NULL} member - * [CritFix] Fix some more URL detector issues - * [CritFix] Fix systemd sockets activation - * [CritFix] Fix unencrypted passwords processing in the controller - * [CritFix] Fix writing CDPs to the database - * [CritFix] Fix writing of encrypted HTTP requests - * [CritFix] Plug memory leak in dkim module - * [CritFix] Plug memory leak in headers getting code - * [CritFix] Pre-filters and post-filters were completely broken - * [CritFix] Properly support SA body regexps - * [CritFix] Really skip filters in case of pre-result set - * [CritFix] Restore the intended pre-filters behaviour - * [Rework] Adopt new maps code - * [Rework] Compile ragel sources when building rspamd - * [Rework] Finish rework for the rest of places that use HTTP - * [Rework] Fix DKIM headers canonicalization - * [Rework] Fix lua maps API - * [Rework] Import linenoise for line editing - * [Rework] Include config structure to all rcl handlers - * [Rework] Make chartable module useful - * [Rework] Move http internal structures to a private header - * [Rework] Partly fix controller - * [Rework] Remove dedicated images list - * [Rework] Rename http proxy to rspamd proxy - * [Rework] Rename mime parts structures - * [Rework] Rework HTTP code - * [Rework] Rework exceptions and newlines processing - * [Rework] Rework pre and postfilters system - * [Rework] Separate method to close backend connections - * [Rework] Start the complete maps rework - * [Rework] Use dynamically generated ragel C sources - * [Feature] Add 'blacklist' and 'strict' modes for whitelists - * [Feature] Add 'symbols_enabled' and 'groups_enabled' to settings - * [Feature] Add ESMTPSA received type - * [Feature] Add a simple script to evaluate rspamd rules in the logs - * [Feature] Add a simple tool to generate DKIM keys - * [Feature] Add a trivial heuristic for codepages - * [Feature] Add and use mumhash for non-crypto hashing - * [Feature] Add better method to check lua userdata types - * [Feature] Add better zip files search algorithm - * [Feature] Add catena PBKDF function - * [Feature] Add configuration knobs for in and out parser scripts - * [Feature] Add content filtering support to multimap - * [Feature] Add different timeouts for proxy connections - * [Feature] Add dot commands for lua REPL: - * [Feature] Add execution of lua global functions script - * [Feature] Add function for pretty printing of inet addresses - * [Feature] Add function to convert fstring_t to c string - * [Feature] Add function to create temporary shared memory mapping - * [Feature] Add function to generate random hex string - * [Feature] Add generic fucnction to parse IP maps - * [Feature] Add initial version of HTTP lua repl - * [Feature] Add learn condition to the default configuration - * [Feature] Add learn conditions for classifiers - * [Feature] Add limit for dkim signatures to be checked - * [Feature] Add locking routines for lua_util - * [Feature] Add lua API for getting info from archives - * [Feature] Add lua utility to decode URL encoding - * [Feature] Add method to copy message from http connection - * [Feature] Add mirrors feature - * [Feature] Add more algorithms for shingles generation - * [Feature] Add more domains to redirectors list - * [Feature] Add more encodingsto cryptobox hash API - * [Feature] Add more file utilities to lua_util - * [Feature] Add more functions to extract data from text parts - * [Feature] Add more methods to get headers from a task - * [Feature] Add more methods to init http message body - * [Feature] Add more options for redis config parsing function - * [Feature] Add new representation of email address - * [Feature] Add new symbols to filter bad extensions in messages - * [Feature] Add new utility methods to mimepart object - * [Feature] Add openphish support to rspamd phishing module - * [Feature] Add parsers for SMTP address in ragel - * [Feature] Add parsing of mirror hosts for fuzzy storage - * [Feature] Add parsing scripts for master connection as well - * [Feature] Add preliminary greylist plugin - * [Feature] Add preliminary phishtank support - * [Feature] Add preliminary rarv5 support - * [Feature] Add preliminary version of ssl toolbox - * [Feature] Add protection against open files limit and accepting sockets - * [Feature] Add rar v4 support - * [Feature] Add reading scripts for master connection - * [Feature] Add replies plugin - * [Feature] Add results parsing code - * [Feature] Add routines to compare and check pubkeys - * [Feature] Add simpler versions of refcounts - * [Feature] Add some time manipulation functions for lua APi - * [Feature] Add support for non-standard BATV signatures - * [Feature] Add support of address with at-domain list - * [Feature] Add support to search archives by magic - * [Feature] Add task:get_rawbody method - * [Feature] Add test to check shared memory support sanity - * [Feature] Add the initial version of LUA repl to rspamadm - * [Feature] Add throughput graph for RRD backend to WebUI - * [Feature] Add universal function to make a proper redis request - * [Feature] Add universal function to parse redis servers for plugins - * [Feature] Add util.unlink function - * [Feature] Add utility function to return random number from 0 to 1 - * [Feature] Add utility method to convert ftok to C string - * [Feature] Add utility to map shared memory segments - * [Feature] Add versions to fuzzy storage - * [Feature] Add workaround for legacy clients in rspamd proxy - * [Feature] Add workaround for systems without sane shmem support - * [Feature] Add xoroshiro+ fast rng for non-crypto purposes - * [Feature] Adopt plugins for new maps API - * [Feature] Allow SPF to be checked for empty tasks - * [Feature] Allow binary patterns in lua_trie - * [Feature] Allow catena encrypted passwords in controller - * [Feature] Allow client ip match in the settings - * [Feature] Allow easy adding and overriding of fuzzy rules - * [Feature] Allow empty tasks to be processed - * [Feature] Allow hostnames in IP maps - * [Feature] Allow https maps - * [Feature] Allow multiple PBKDF types in `rspamadm pw` - * [Feature] Allow named fuzzy rules - * [Feature] Allow non zero terminated patterns in multipattern - * [Feature] Allow partial hash updates - * [Feature] Allow pipelining for redis.make_request - * [Feature] Allow sending empty requests using client - * [Feature] Allow setting fuzzy flag by symbol not by value - * [Feature] Allow setting scores and actions from lua - * [Feature] Allow shared memory simple http client - * [Feature] Allow static lua files in any parts of rspamd sources - * [Feature] Allow to change flag from fuzzy learn condition - * [Feature] Allow to check rspamd_text using maps - * [Feature] Allow to disable composite rules from settings - * [Feature] Allow to disable some modules from common redis setup - * [Feature] Allow to extract ucl_object from lua using common API - * [Feature] Allow to get settings and settings id hash from lua_task - * [Feature] Allow to have specific settings for mirrored traffic - * [Feature] Allow to open message from a shared memory segment - * [Feature] Allow to parse pubkeys from the rcl config - * [Feature] Allow to pass extradata from rspamd to rmilter - * [Feature] Allow to query storage about number of fuzzy hashes stored - * [Feature] Allow to read logs without symbols scores - * [Feature] Allow to read password from console for rspamc - * [Feature] Allow to set ciphers and CA paths in config - * [Feature] Allow to skip some initialization phases to speed up rspamadm - * [Feature] Allow underscore separated names in settings - * [Feature] Allow versioning for sqlite databases - * [Feature] Always allow to terminate rspamd - * [Feature] Better deal with backend errors - * [Feature] Better lua_redis logging - * [Feature] Configure CA path and ciphers - * [Feature] Create a dedicated parser to strip newlines - * [Feature] Deduplicate the same urls in multimap module - * [Feature] Distinguish luajit from lua - * [Feature] Do not print garbadge in --compact output - * [Feature] Dynamically detect if a CPU is incompatible with hyperscan - * [Feature] Enable forced resolving for some lua plugins - * [Feature] Enable rrd by default - * [Feature] Enable workaround for exim - * [Feature] Fix task functions to work without rspamd_config - * [Feature] Further improvements to chartable module - * [Feature] Further micro-optimizations for hashing and shingles - * [Feature] Further relax parser - * [Feature] Humanize numbers in stats widgets - * [Feature] Implement HTTPS client - * [Feature] Implement SSL support in http client - * [Feature] Implement body rules for the trie plugin - * [Feature] Implement braced regexp quantifiers - * [Feature] Implement compare scripts for mirrors results - * [Feature] Implement compare scripts setup - * [Feature] Implement composites policies - * [Feature] Implement conditional learning for classifiers - * [Feature] Implement constructing of map from UCL - * [Feature] Implement dkim signing in dkim check plugin - * [Feature] Implement fuzzy storage updates - * [Feature] Implement fuzzy updates push protocol - * [Feature] Implement https maps - * [Feature] Implement inter-process maps caching - * [Feature] Implement mapping of remote flags to local flags - * [Feature] Implement mirroring in rspamd proxy - * [Feature] Implement multi-flags fuzzy replies - * [Feature] Implement multiple-sources fuzzy storage - * [Feature] Implement order of pre/post filters - * [Feature] Implement partial deleting for multi-flags - * [Feature] Implement pipelining for redis async interface - * [Feature] Implement ragel parser for received headers - * [Feature] Implement reading of messages to shared memory - * [Feature] Implement refcount for messages - * [Feature] Implement retransmits for master connection - * [Feature] Implement zero-copy mode for HTTP reading - * [Feature] Improve SPF domain detection logic - * [Feature] Improve config:register_symbol function - * [Feature] Improve error report for type mismatch in lua - * [Feature] Improve fstrings API - * [Feature] Improve getting SMTP data from lua_task - * [Feature] Improve levenshtein distance function - * [Feature] Improve logging in proxy and add refcounts - * [Feature] Improve logging lua types - * [Feature] Improve master/slave logging - * [Feature] Improve phishing plugin - * [Feature] Improve phishtank and openphish support - * [Feature] Improve ragel build target - * [Feature] Improve statistics script - * [Feature] Initialize ssl library to use SSL connections - * [Feature] Interpolate points sent to webui - * [Feature] Limit logging of elements that could have too many items - * [Feature] Lock ANN file when loading - * [Feature] New abstract hashing API in cryptobox - * [Feature] Normalize quoted addresses - * [Feature] Now cryptobox lua API accepts rspamd text as input - * [Feature] Optimize alignment to speed up hashing - * [Feature] Parse received date and ESMTPA proto - * [Feature] Parse received timestamp - * [Feature] Pass settings id to log helper - * [Feature] Pass settings id to lua script from log helper - * [Feature] Perform files expansion on proxying - * [Feature] Preliminary implementation of fuzzy master/slave updates - * [Feature] Print userdata using tostring if possible - * [Feature] Propagate HTTP result string when using proxy - * [Feature] Properly implement unweighted round-robin algorithm - * [Feature] Reduce number of timers queries - * [Feature] Rework and improve fuzzy storage - * [Feature] Rework dns resolving API for lua, add 'forced' option - * [Feature] Rework fann module to understand settings - * [Feature] Rework listening system to allow multiple socket types per worker - * [Feature] Rework ratelimit module to set expiration - * [Feature] Save bayes probability in memory pool var - * [Feature] Save settings id hash for convenience - * [Feature] Search for SSL_set_tlsext_host_name support - * [Feature] Send DKIM signature to protocol reply - * [Feature] Show DKIM signature in rspamc client - * [Feature] Show symbols description in scan output - * [Feature] Sign message merely after DKIM check - * [Feature] Simplify machines by assuming that headers are unfolded - * [Feature] Sort symbols when displaying them in log - * [Feature] Split main connection from mirrored connections - * [Feature] Start moving to the new email address structure - * [Feature] Store HTTP headers in a hash table - * [Feature] Store more information about compressed files - * [Feature] Store raw headers value to use them in DKIM - * [Feature] Store text parts content with newlines stripped - * [Feature] Support DKIM signing - * [Feature] Support EXIF jpeg images - * [Feature] Support archive files list extraction - * [Feature] Support archives when matching patterns in multimap - * [Feature] Support premium/academic feed for openphish - * [Feature] Support rspamd_updates via https - * [Feature] Supprort FQDNs in phishing module maps - * [Feature] Try to read on fuzzy timeout to avoid fake timeouts - * [Feature] Try to select the optimal possible function for input - * [Feature] Unescape and unquote smtp addresses - * [Feature] Update fuzzy timestamp when adding value - * [Feature] Update mumhash - * [Feature] Use -flto if possible when optimizations are enabled - * [Feature] Use extended map types in lua map, unify code - * [Feature] Use file lock in logger to avoid deadlocks - * [Feature] Use generic global string split function - * [Feature] Use metrohash as well - * [Feature] Use mumhash by default for incremental hashing - * [Feature] Use mumhash for words hashing - * [Feature] Use new ip parsing API - * [Feature] Use new maps API for local addrs - * [Feature] Use new ragel parser in message parsing code - * [Feature] Use new received parser instead of old one - * [Feature] Use new redis API in DMARC plugin - * [Feature] Use new redis API in greylist plugin - * [Feature] Use new redis API in ip_score plugin - * [Feature] Use new redis API in ratelimit plugin - * [Feature] Use new redis API in replies plugin - * [Feature] Use new version of register_symbol in rspamd plugins - * [Feature] Use offset when passing shm to deal with encrypted requests - * [Feature] Use one pass to remove newlines and store their positions - * [Feature] Use rspamd specific type checks for userdata - * [Feature] Use shared memory storage for http maps - * [Feature] Use universal redis definitions in rspamd plugins - * [Feature] Various improvements in greylist module - * [Feature] Wait for sqlite if locked when switching to WAL mode - * [Fix] Add filenames sanity filtering for mime types - * [Fix] Add guards for empty parts - * [Fix] Add missing types - * [Fix] Add more priority for config file symbols registered from UCL - * [Fix] Add sanity checks when compiling regexp maps - * [Fix] Add spaces instead of newlines to the normalized content - * [Fix] Add workaround for ancient openssl - * [Fix] Add workaround for gmime CTE stupidity - * [Fix] Add workaround for hex digits - * [Fix] Adjust MISSING_MIMEOLE score - * [Fix] Adjust body/body_buf when stealing encrypted message - * [Fix] Adopt lua task API for the new email addresses structure - * [Fix] Allow for disabling DMARC reporting when Redis is configured - * [Fix] Always register openphish and phishtank virtual symbols - * [Fix] Always use shmem on linux - * [Fix] Another change of newlines policy - * [Fix] Another d3evolution update - * [Fix] Another fix for exim workaround - * [Fix] Another fix for legacy clients - * [Fix] Another fix for maps scheduling - * [Fix] Another fix for marking upstreams inactive - * [Fix] Another fix for postfilters - * [Fix] Another fix for redis timeouts - * [Fix] Avoid `table.getn` method as it has been removed in lua 5.3 - * [Fix] Avoid double hashing for images - * [Fix] Avoid linking with actrie if hyperscan is enabled - * [Fix] Check copy result when sending message to mirrors - * [Fix] Cleanup message when assiging body - * [Fix] Cleanup stack from global vars - * [Fix] Correctly parse query type - * [Fix] Disable all symbols if enable_groups is found in settings - * [Fix] Disable fts as it is completely broken in bloody linux - * [Fix] Disable multiple autolearn checks - * [Fix] Disallow updates by default - * [Fix] Do not abort when cannot load a map - * [Fix] Do not check recursion for non-DNS SPF record types - * [Fix] Do not delete uninitialized events - * [Fix] Do not die if shmem_mkstemp fails - * [Fix] Do not die when no metrics defined - * [Fix] Do not even try pcre in case of regexp maps - * [Fix] Do not greylist messages if redis has failed somehow - * [Fix] Do not greylist on rejection - * [Fix] Do not leave temporary maps cached - * [Fix] Do not output meaningless errors - * [Fix] Do not send NaN in json - * [Fix] Don't mix hyperscan and pcre processing within a same task - * [Fix] Finally rework and simplify redis backend for statistics - * [Fix] Fix Exim shutdown patch - * [Fix] Fix JIT compilation for PCRE2 expressions - * [Fix] Fix JIT usage for PCRE2 - * [Fix] Fix REPL output - * [Fix] Fix SMTP address parsing machine - * [Fix] Fix UTF8 mode in PCRE2 - * [Fix] Fix a stupid misprint in word 'phishing' - * [Fix] Fix accepting the first update when local idx is -1 - * [Fix] Fix adding maps from ucl - * [Fix] Fix adding upstream to an active queue - * [Fix] Fix and rescore R_PARTS_DIFFER logic - * [Fix] Fix body rules in SA plugin - * [Fix] Fix body start position - * [Fix] Fix border case in urls detector - * [Fix] Fix border cases for incremental hashing - * [Fix] Fix caseless uthash application - * [Fix] Fix chartable issue with starting digits - * [Fix] Fix client_ip in users settings - * [Fix] Fix compilation issue - * [Fix] Fix conditional learning - * [Fix] Fix crash on empty maps - * [Fix] Fix creating of URLs from LUA - * [Fix] Fix creating of temporary shmem segment - * [Fix] Fix creation of mmapped statfiles - * [Fix] Fix descriptors leak on shmem detaching - * [Fix] Fix detaching of shared memory segments - * [Fix] Fix detection of URLs in text parts - * [Fix] Fix directories processing for rspamc - * [Fix] Fix displaying of rewrite subject in WebUI - * [Fix] Fix dkim private keys operations - * [Fix] Fix dkim signing - * [Fix] Fix dynamic scoring of subject symbols - * [Fix] Fix email address build - * [Fix] Fix encrypted proxy requests - * [Fix] Fix errors counting in upstreams - * [Fix] Fix errors handling in the proxy - * [Fix] Fix event bases for IO events - * [Fix] Fix events handling when scheduling map wacth - * [Fix] Fix fann rewrite - * [Fix] Fix files fallback for shmem transfer - * [Fix] Fix fuzzy adding in webui - * [Fix] Fix fuzzy storage encrypted mirroring - * [Fix] Fix fuzzy storage sync replies - * [Fix] Fix handling of the same words - * [Fix] Fix inserting values to the sources list - * [Fix] Fix ipv6 mask application - * [Fix] Fix issue with missing recipients - * [Fix] Fix issues with multiple returns from lua - * [Fix] Fix learning for non-existent backend - * [Fix] Fix legacy clients support in proxy - * [Fix] Fix length calculations for shared memory segments - * [Fix] Fix listening on UDP sockets - * [Fix] Fix loading of file maps - * [Fix] Fix long regexp flags (e.g. {sa_body}) - * [Fix] Fix lua compare function init - * [Fix] Fix maps descriptions - * [Fix] Fix maps locking - * [Fix] Fix max_train setup in ANN module - * [Fix] Fix memory corruption - * [Fix] Fix memory leak in unsigned maps reading - * [Fix] Fix misprints for lto usage - * [Fix] Fix more issues with scripts processing - * [Fix] Fix next-to-last extension length check - * [Fix] Fix openssl initialization - * [Fix] Fix order of arguments - * [Fix] Fix order of initialization - * [Fix] Fix parser - * [Fix] Fix parsing of binary tries - * [Fix] Fix parsing of braced IPv6 addresses - * [Fix] Fix parsing of nested braces in SMTP comments - * [Fix] Fix parsing of rarv5 archives - * [Fix] Fix periodic scheduling when a map is not modified - * [Fix] Fix possible FP in TRACKER_ID rule - * [Fix] Fix post-filters processing - * [Fix] Fix potential NULL dereference - * [Fix] Fix processing of <br> and <hr> tags - * [Fix] Fix processing of addresses in protocol - * [Fix] Fix processing of messages without received headers - * [Fix] Fix proxying issue for unconnected sessions - * [Fix] Fix proxying of the encrypted messages - * [Fix] Fix race condition with shared memory by refcounts - * [Fix] Fix ratelimit initialization - * [Fix] Fix redis set request in replies plugin - * [Fix] Fix redis timeout events handling - * [Fix] Fix redis timeouts processing logic - * [Fix] Fix refcounts in lua_redis - * [Fix] Fix results checking if no master connection is active - * [Fix] Fix return value for couple of lua functions - * [Fix] Fix round-robin selection when upstreams have no weight - * [Fix] Fix rows calculation in graph - * [Fix] Fix rspamd_redis_make_request syntax in replies plugin - * [Fix] Fix scheduling of locked map events - * [Fix] Fix scores detection - * [Fix] Fix searching for newline positions - * [Fix] Fix secure_ip setting in controller - * [Fix] Fix sending data to graph command - * [Fix] Fix setting of score for parts differ - * [Fix] Fix setting of the lua top - * [Fix] Fix setting path for lua - * [Fix] Fix setting path for phishtank - * [Fix] Fix settings application - * [Fix] Fix shm_open call as described in POSIX - * [Fix] Fix size of length in fuzzy mirror wire protocol - * [Fix] Fix smtp grammar issues - * [Fix] Fix some issues with redis API - * [Fix] Fix some issues with retries in the proxy - * [Fix] Fix stack growing - * [Fix] Fix start of body detection in DKIM - * [Fix] Fix state on timeout - * [Fix] Fix stats script - * [Fix] Fix substring search when there are '\0' in strings - * [Fix] Fix symbol name for spf soft fail - * [Fix] Fix symbol type's check - * [Fix] Fix symbols registration and execution - * [Fix] Fix the case of multiple values keys - * [Fix] Fix the default symbol names according to metric - * [Fix] Fix timeout setup on learning - * [Fix] Fix timeouts in redis cache processing - * [Fix] Fix timeouts processing in lua_redis - * [Fix] Fix upstreams interaction for rspamd proxy - * [Fix] Fix usage of rdns reply structure - * [Fix] Fix varargs loop - * [Fix] Fix whitelists and blacklists in SA rules - * [Fix] Fix write servers setup for redis - * [Fix] Fix writing of HTTP messages - * [Fix] Force rspamd to upgrade fuzzy db on opening - * [Fix] Free the correct pointer - * [Fix] Further fixes for lto and static linking - * [Fix] Further fixes for surbl extensions map - * [Fix] Further fixes in maps code - * [Fix] Further improvements to error messages in fuzzy check - * [Fix] Further tweaks to redis garbadge collection - * [Fix] Groups are now case insensitive - * [Fix] Handle log pipe read errors - * [Fix] Handle nested dependencies in SpamAssassin plugin - * [Fix] Implement new automata to skip empty lines for dkim signing - * [Fix] Improve error messages on fuzzy add - * [Fix] Improve lua redis handling - * [Fix] Improve phishing module logging - * [Fix] Improve printing of fuzzy errors - * [Fix] Improve rrd diagnostic errors - * [Fix] Improve strcase hash used in uthash - * [Fix] Include fuzzy key to distinguish storages with different keys - * [Fix] Include slave cluster name into http request - * [Fix] Include some more information about archives - * [Fix] Indicate upstream error on timeout - * [Fix] Initialize hash tables array to avoid crashes - * [Fix] Initialize parser scripts properly - * [Fix] Initialize vars to avoid warnings - * [Fix] Inverse logic for saving ANN - * [Fix] Link lpeg to rspamd lua library - * [Fix] Make extension checks case-insensitive - * [Fix] Mark expired hashes as not found and not as zero flag - * [Fix] Match archive name as well - * [Fix] More and more fixes to redis states - * [Fix] More fixes about shared memory in proxy - * [Fix] More fixes for redis refcounts - * [Fix] More fixes to end of headers detection - * [Fix] More fixes to events logic - * [Fix] More fixes to multi-flag fuzzy storage - * [Fix] More fixes to parts distance calculations - * [Fix] More guards for redis free - * [Fix] One more fix in redis destructor - * [Fix] One more try to fix redis - * [Fix] PIE is required for static build - * [Fix] Partial fix for mmap'd statistics tests - * [Fix] Plug memory leak in proxy - * [Fix] Properly detect end of headers position - * [Fix] Properly init and free session structures - * [Fix] Reduce PRECEDENCE_BULK rule weight - * [Fix] Reduce the default thresholds for learning - * [Fix] Remove Type=forking from systemd unit file (#709) - * [Fix] Remove bad FANN file to save computational resources - * [Fix] Remove event before closing of fd to avoid race conditions - * [Fix] Remove parsing of 'from' variable in redis backend - * [Fix] Remove some bad domains from whitelists - * [Fix] Repair optional dependencies - * [Fix] Reset master connection when retransmitting scan request - * [Fix] Restore ONCE_RECEIVED symbol - * [Fix] Restore compatibility with old lua API behaviour - * [Fix] Restore redis runtime state - * [Fix] Reverse options when received - * [Fix] Send updates to mirrors only if we have some changes - * [Fix] Set host attribute properly when making HTTP request from lua - * [Fix] Set terminated state before calling of async free - * [Fix] Simplify MISSING_MIMEOLE rule - * [Fix] Simplify state machine by ignoring multiple spaces - * [Fix] Skip setting RPATH for static builds - * [Fix] Slightly reduce weights of rules with high FP rate - * [Fix] Some fixes to libmagic initialization - * [Fix] Some more fixes to ratelimit plugin - * [Fix] Strip '\r\n' properly - * [Fix] Switch hashes to mumhash - * [Fix] Treat NaN values properly in graph command - * [Fix] Try to avoid FP when checking for phished URLs - * [Fix] Try to avoid recursive events deletions - * [Fix] Try to fix false positive URL detections in text parts - * [Fix] Try to fix issue in redis stats backend when task is closed - * [Fix] Try to fix proxying of stupid spamc protocol to HTTP mirrors - * [Fix] Try to fix redis crashes - * [Fix] Try to fix upstreams with one element - * [Fix] Try to handle multiline history in a more sane way - * [Fix] Unbreak build on gcc < 4.9 - * [Fix] Update RPM spec/sources (#700) - * [Fix] Update d3evolution version - * [Fix] Update mumhash implementation - * [Fix] Use custom error function for pre and post filters - * [Fix] Use new postfilters and prefilters API in the plugins - * [Fix] Use non-blocking mode for systemd sockets - * [Fix] Use shared memory merely for local backends in the proxy - * [Fix] Use watchers for spf plugin - * [Fix] Varioud fixes to the maps code + * [CritFix] Fix SA rawbody processing - exclude top part + * [CritFix] Fix decoding of UTF HTML entitles + * [CritFix] Fix encrypted fuzzy requests + * [CritFix] Fix leak of shared memory fds and files + * [CritFix] Fix levenshtein distance calculations + * [CritFix] Fix mime headers processing + * [CritFix] Fix parsing of URLs in texts + * [CritFix] Fix parsing of missing classes + * [CritFix] Fix redis structure by adding {NULL, NULL} member + * [CritFix] Fix some more URL detector issues + * [CritFix] Fix systemd sockets activation + * [CritFix] Fix unencrypted passwords processing in the controller + * [CritFix] Fix writing CDPs to the database + * [CritFix] Fix writing of encrypted HTTP requests + * [CritFix] Plug memory leak in dkim module + * [CritFix] Plug memory leak in headers getting code + * [CritFix] Pre-filters and post-filters were completely broken + * [CritFix] Properly support SA body regexps + * [CritFix] Really skip filters in case of pre-result set + * [CritFix] Restore the intended pre-filters behaviour + * [Rework] Adopt new maps code + * [Rework] Compile ragel sources when building rspamd + * [Rework] Finish rework for the rest of places that use HTTP + * [Rework] Fix DKIM headers canonicalization + * [Rework] Fix lua maps API + * [Rework] Import linenoise for line editing + * [Rework] Include config structure to all rcl handlers + * [Rework] Make chartable module useful + * [Rework] Move http internal structures to a private header + * [Rework] Partly fix controller + * [Rework] Remove dedicated images list + * [Rework] Rename http proxy to rspamd proxy + * [Rework] Rename mime parts structures + * [Rework] Rework HTTP code + * [Rework] Rework exceptions and newlines processing + * [Rework] Rework pre and postfilters system + * [Rework] Separate method to close backend connections + * [Rework] Start the complete maps rework + * [Rework] Use dynamically generated ragel C sources + * [Feature] Add 'blacklist' and 'strict' modes for whitelists + * [Feature] Add 'symbols_enabled' and 'groups_enabled' to settings + * [Feature] Add ESMTPSA received type + * [Feature] Add a simple script to evaluate rspamd rules in the logs + * [Feature] Add a simple tool to generate DKIM keys + * [Feature] Add a trivial heuristic for codepages + * [Feature] Add and use mumhash for non-crypto hashing + * [Feature] Add better method to check lua userdata types + * [Feature] Add better zip files search algorithm + * [Feature] Add catena PBKDF function + * [Feature] Add configuration knobs for in and out parser scripts + * [Feature] Add content filtering support to multimap + * [Feature] Add different timeouts for proxy connections + * [Feature] Add dot commands for lua REPL: + * [Feature] Add execution of lua global functions script + * [Feature] Add function for pretty printing of inet addresses + * [Feature] Add function to convert fstring_t to c string + * [Feature] Add function to create temporary shared memory mapping + * [Feature] Add function to generate random hex string + * [Feature] Add generic fucnction to parse IP maps + * [Feature] Add initial version of HTTP lua repl + * [Feature] Add learn condition to the default configuration + * [Feature] Add learn conditions for classifiers + * [Feature] Add limit for dkim signatures to be checked + * [Feature] Add locking routines for lua_util + * [Feature] Add lua API for getting info from archives + * [Feature] Add lua utility to decode URL encoding + * [Feature] Add method to copy message from http connection + * [Feature] Add mirrors feature + * [Feature] Add more algorithms for shingles generation + * [Feature] Add more domains to redirectors list + * [Feature] Add more encodingsto cryptobox hash API + * [Feature] Add more file utilities to lua_util + * [Feature] Add more functions to extract data from text parts + * [Feature] Add more methods to get headers from a task + * [Feature] Add more methods to init http message body + * [Feature] Add more options for redis config parsing function + * [Feature] Add new representation of email address + * [Feature] Add new symbols to filter bad extensions in messages + * [Feature] Add new utility methods to mimepart object + * [Feature] Add openphish support to rspamd phishing module + * [Feature] Add parsers for SMTP address in ragel + * [Feature] Add parsing of mirror hosts for fuzzy storage + * [Feature] Add parsing scripts for master connection as well + * [Feature] Add preliminary greylist plugin + * [Feature] Add preliminary phishtank support + * [Feature] Add preliminary rarv5 support + * [Feature] Add preliminary version of ssl toolbox + * [Feature] Add protection against open files limit and accepting sockets + * [Feature] Add rar v4 support + * [Feature] Add reading scripts for master connection + * [Feature] Add replies plugin + * [Feature] Add results parsing code + * [Feature] Add routines to compare and check pubkeys + * [Feature] Add simpler versions of refcounts + * [Feature] Add some time manipulation functions for lua APi + * [Feature] Add support for non-standard BATV signatures + * [Feature] Add support of address with at-domain list + * [Feature] Add support to search archives by magic + * [Feature] Add task:get_rawbody method + * [Feature] Add test to check shared memory support sanity + * [Feature] Add the initial version of LUA repl to rspamadm + * [Feature] Add throughput graph for RRD backend to WebUI + * [Feature] Add universal function to make a proper redis request + * [Feature] Add universal function to parse redis servers for plugins + * [Feature] Add util.unlink function + * [Feature] Add utility function to return random number from 0 to 1 + * [Feature] Add utility method to convert ftok to C string + * [Feature] Add utility to map shared memory segments + * [Feature] Add versions to fuzzy storage + * [Feature] Add workaround for legacy clients in rspamd proxy + * [Feature] Add workaround for systems without sane shmem support + * [Feature] Add xoroshiro+ fast rng for non-crypto purposes + * [Feature] Adopt plugins for new maps API + * [Feature] Allow SPF to be checked for empty tasks + * [Feature] Allow binary patterns in lua_trie + * [Feature] Allow catena encrypted passwords in controller + * [Feature] Allow client ip match in the settings + * [Feature] Allow easy adding and overriding of fuzzy rules + * [Feature] Allow empty tasks to be processed + * [Feature] Allow hostnames in IP maps + * [Feature] Allow https maps + * [Feature] Allow multiple PBKDF types in `rspamadm pw` + * [Feature] Allow named fuzzy rules + * [Feature] Allow non zero terminated patterns in multipattern + * [Feature] Allow partial hash updates + * [Feature] Allow pipelining for redis.make_request + * [Feature] Allow sending empty requests using client + * [Feature] Allow setting fuzzy flag by symbol not by value + * [Feature] Allow setting scores and actions from lua + * [Feature] Allow shared memory simple http client + * [Feature] Allow static lua files in any parts of rspamd sources + * [Feature] Allow to change flag from fuzzy learn condition + * [Feature] Allow to check rspamd_text using maps + * [Feature] Allow to disable composite rules from settings + * [Feature] Allow to disable some modules from common redis setup + * [Feature] Allow to extract ucl_object from lua using common API + * [Feature] Allow to get settings and settings id hash from lua_task + * [Feature] Allow to have specific settings for mirrored traffic + * [Feature] Allow to open message from a shared memory segment + * [Feature] Allow to parse pubkeys from the rcl config + * [Feature] Allow to pass extradata from rspamd to rmilter + * [Feature] Allow to query storage about number of fuzzy hashes stored + * [Feature] Allow to read logs without symbols scores + * [Feature] Allow to read password from console for rspamc + * [Feature] Allow to set ciphers and CA paths in config + * [Feature] Allow to skip some initialization phases to speed up rspamadm + * [Feature] Allow underscore separated names in settings + * [Feature] Allow versioning for sqlite databases + * [Feature] Always allow to terminate rspamd + * [Feature] Better deal with backend errors + * [Feature] Better lua_redis logging + * [Feature] Configure CA path and ciphers + * [Feature] Create a dedicated parser to strip newlines + * [Feature] Deduplicate the same urls in multimap module + * [Feature] Distinguish luajit from lua + * [Feature] Do not print garbadge in --compact output + * [Feature] Dynamically detect if a CPU is incompatible with hyperscan + * [Feature] Enable forced resolving for some lua plugins + * [Feature] Enable rrd by default + * [Feature] Enable workaround for exim + * [Feature] Fix task functions to work without rspamd_config + * [Feature] Further improvements to chartable module + * [Feature] Further micro-optimizations for hashing and shingles + * [Feature] Further relax parser + * [Feature] Humanize numbers in stats widgets + * [Feature] Implement HTTPS client + * [Feature] Implement SSL support in http client + * [Feature] Implement body rules for the trie plugin + * [Feature] Implement braced regexp quantifiers + * [Feature] Implement compare scripts for mirrors results + * [Feature] Implement compare scripts setup + * [Feature] Implement composites policies + * [Feature] Implement conditional learning for classifiers + * [Feature] Implement constructing of map from UCL + * [Feature] Implement dkim signing in dkim check plugin + * [Feature] Implement fuzzy storage updates + * [Feature] Implement fuzzy updates push protocol + * [Feature] Implement https maps + * [Feature] Implement inter-process maps caching + * [Feature] Implement mapping of remote flags to local flags + * [Feature] Implement mirroring in rspamd proxy + * [Feature] Implement multi-flags fuzzy replies + * [Feature] Implement multiple-sources fuzzy storage + * [Feature] Implement order of pre/post filters + * [Feature] Implement partial deleting for multi-flags + * [Feature] Implement pipelining for redis async interface + * [Feature] Implement ragel parser for received headers + * [Feature] Implement reading of messages to shared memory + * [Feature] Implement refcount for messages + * [Feature] Implement retransmits for master connection + * [Feature] Implement zero-copy mode for HTTP reading + * [Feature] Improve SPF domain detection logic + * [Feature] Improve config:register_symbol function + * [Feature] Improve error report for type mismatch in lua + * [Feature] Improve fstrings API + * [Feature] Improve getting SMTP data from lua_task + * [Feature] Improve levenshtein distance function + * [Feature] Improve logging in proxy and add refcounts + * [Feature] Improve logging lua types + * [Feature] Improve master/slave logging + * [Feature] Improve phishing plugin + * [Feature] Improve phishtank and openphish support + * [Feature] Improve ragel build target + * [Feature] Improve statistics script + * [Feature] Initialize ssl library to use SSL connections + * [Feature] Interpolate points sent to webui + * [Feature] Limit logging of elements that could have too many items + * [Feature] Lock ANN file when loading + * [Feature] New abstract hashing API in cryptobox + * [Feature] Normalize quoted addresses + * [Feature] Now cryptobox lua API accepts rspamd text as input + * [Feature] Optimize alignment to speed up hashing + * [Feature] Parse received date and ESMTPA proto + * [Feature] Parse received timestamp + * [Feature] Pass settings id to log helper + * [Feature] Pass settings id to lua script from log helper + * [Feature] Perform files expansion on proxying + * [Feature] Preliminary implementation of fuzzy master/slave updates + * [Feature] Print userdata using tostring if possible + * [Feature] Propagate HTTP result string when using proxy + * [Feature] Properly implement unweighted round-robin algorithm + * [Feature] Reduce number of timers queries + * [Feature] Rework and improve fuzzy storage + * [Feature] Rework dns resolving API for lua, add 'forced' option + * [Feature] Rework fann module to understand settings + * [Feature] Rework listening system to allow multiple socket types per worker + * [Feature] Rework ratelimit module to set expiration + * [Feature] Save bayes probability in memory pool var + * [Feature] Save settings id hash for convenience + * [Feature] Search for SSL_set_tlsext_host_name support + * [Feature] Send DKIM signature to protocol reply + * [Feature] Show DKIM signature in rspamc client + * [Feature] Show symbols description in scan output + * [Feature] Sign message merely after DKIM check + * [Feature] Simplify machines by assuming that headers are unfolded + * [Feature] Sort symbols when displaying them in log + * [Feature] Split main connection from mirrored connections + * [Feature] Start moving to the new email address structure + * [Feature] Store HTTP headers in a hash table + * [Feature] Store more information about compressed files + * [Feature] Store raw headers value to use them in DKIM + * [Feature] Store text parts content with newlines stripped + * [Feature] Support DKIM signing + * [Feature] Support EXIF jpeg images + * [Feature] Support archive files list extraction + * [Feature] Support archives when matching patterns in multimap + * [Feature] Support premium/academic feed for openphish + * [Feature] Support rspamd_updates via https + * [Feature] Supprort FQDNs in phishing module maps + * [Feature] Try to read on fuzzy timeout to avoid fake timeouts + * [Feature] Try to select the optimal possible function for input + * [Feature] Unescape and unquote smtp addresses + * [Feature] Update fuzzy timestamp when adding value + * [Feature] Update mumhash + * [Feature] Use -flto if possible when optimizations are enabled + * [Feature] Use extended map types in lua map, unify code + * [Feature] Use file lock in logger to avoid deadlocks + * [Feature] Use generic global string split function + * [Feature] Use metrohash as well + * [Feature] Use mumhash by default for incremental hashing + * [Feature] Use mumhash for words hashing + * [Feature] Use new ip parsing API + * [Feature] Use new maps API for local addrs + * [Feature] Use new ragel parser in message parsing code + * [Feature] Use new received parser instead of old one + * [Feature] Use new redis API in DMARC plugin + * [Feature] Use new redis API in greylist plugin + * [Feature] Use new redis API in ip_score plugin + * [Feature] Use new redis API in ratelimit plugin + * [Feature] Use new redis API in replies plugin + * [Feature] Use new version of register_symbol in rspamd plugins + * [Feature] Use offset when passing shm to deal with encrypted requests + * [Feature] Use one pass to remove newlines and store their positions + * [Feature] Use rspamd specific type checks for userdata + * [Feature] Use shared memory storage for http maps + * [Feature] Use universal redis definitions in rspamd plugins + * [Feature] Various improvements in greylist module + * [Feature] Wait for sqlite if locked when switching to WAL mode + * [Fix] Add filenames sanity filtering for mime types + * [Fix] Add guards for empty parts + * [Fix] Add missing types + * [Fix] Add more priority for config file symbols registered from UCL + * [Fix] Add sanity checks when compiling regexp maps + * [Fix] Add spaces instead of newlines to the normalized content + * [Fix] Add workaround for ancient openssl + * [Fix] Add workaround for gmime CTE stupidity + * [Fix] Add workaround for hex digits + * [Fix] Adjust MISSING_MIMEOLE score + * [Fix] Adjust body/body_buf when stealing encrypted message + * [Fix] Adopt lua task API for the new email addresses structure + * [Fix] Allow for disabling DMARC reporting when Redis is configured + * [Fix] Always register openphish and phishtank virtual symbols + * [Fix] Always use shmem on linux + * [Fix] Another change of newlines policy + * [Fix] Another d3evolution update + * [Fix] Another fix for exim workaround + * [Fix] Another fix for legacy clients + * [Fix] Another fix for maps scheduling + * [Fix] Another fix for marking upstreams inactive + * [Fix] Another fix for postfilters + * [Fix] Another fix for redis timeouts + * [Fix] Avoid `table.getn` method as it has been removed in lua 5.3 + * [Fix] Avoid double hashing for images + * [Fix] Avoid linking with actrie if hyperscan is enabled + * [Fix] Check copy result when sending message to mirrors + * [Fix] Cleanup message when assiging body + * [Fix] Cleanup stack from global vars + * [Fix] Correctly parse query type + * [Fix] Disable all symbols if enable_groups is found in settings + * [Fix] Disable fts as it is completely broken in bloody linux + * [Fix] Disable multiple autolearn checks + * [Fix] Disallow updates by default + * [Fix] Do not abort when cannot load a map + * [Fix] Do not check recursion for non-DNS SPF record types + * [Fix] Do not delete uninitialized events + * [Fix] Do not die if shmem_mkstemp fails + * [Fix] Do not die when no metrics defined + * [Fix] Do not even try pcre in case of regexp maps + * [Fix] Do not greylist messages if redis has failed somehow + * [Fix] Do not greylist on rejection + * [Fix] Do not leave temporary maps cached + * [Fix] Do not output meaningless errors + * [Fix] Do not send NaN in json + * [Fix] Don't mix hyperscan and pcre processing within a same task + * [Fix] Finally rework and simplify redis backend for statistics + * [Fix] Fix Exim shutdown patch + * [Fix] Fix JIT compilation for PCRE2 expressions + * [Fix] Fix JIT usage for PCRE2 + * [Fix] Fix REPL output + * [Fix] Fix SMTP address parsing machine + * [Fix] Fix UTF8 mode in PCRE2 + * [Fix] Fix a stupid misprint in word 'phishing' + * [Fix] Fix accepting the first update when local idx is -1 + * [Fix] Fix adding maps from ucl + * [Fix] Fix adding upstream to an active queue + * [Fix] Fix and rescore R_PARTS_DIFFER logic + * [Fix] Fix body rules in SA plugin + * [Fix] Fix body start position + * [Fix] Fix border case in urls detector + * [Fix] Fix border cases for incremental hashing + * [Fix] Fix caseless uthash application + * [Fix] Fix chartable issue with starting digits + * [Fix] Fix client_ip in users settings + * [Fix] Fix compilation issue + * [Fix] Fix conditional learning + * [Fix] Fix crash on empty maps + * [Fix] Fix creating of URLs from LUA + * [Fix] Fix creating of temporary shmem segment + * [Fix] Fix creation of mmapped statfiles + * [Fix] Fix descriptors leak on shmem detaching + * [Fix] Fix detaching of shared memory segments + * [Fix] Fix detection of URLs in text parts + * [Fix] Fix directories processing for rspamc + * [Fix] Fix displaying of rewrite subject in WebUI + * [Fix] Fix dkim private keys operations + * [Fix] Fix dkim signing + * [Fix] Fix dynamic scoring of subject symbols + * [Fix] Fix email address build + * [Fix] Fix encrypted proxy requests + * [Fix] Fix errors counting in upstreams + * [Fix] Fix errors handling in the proxy + * [Fix] Fix event bases for IO events + * [Fix] Fix events handling when scheduling map wacth + * [Fix] Fix fann rewrite + * [Fix] Fix files fallback for shmem transfer + * [Fix] Fix fuzzy adding in webui + * [Fix] Fix fuzzy storage encrypted mirroring + * [Fix] Fix fuzzy storage sync replies + * [Fix] Fix handling of the same words + * [Fix] Fix inserting values to the sources list + * [Fix] Fix ipv6 mask application + * [Fix] Fix issue with missing recipients + * [Fix] Fix issues with multiple returns from lua + * [Fix] Fix learning for non-existent backend + * [Fix] Fix legacy clients support in proxy + * [Fix] Fix length calculations for shared memory segments + * [Fix] Fix listening on UDP sockets + * [Fix] Fix loading of file maps + * [Fix] Fix long regexp flags (e.g. {sa_body}) + * [Fix] Fix lua compare function init + * [Fix] Fix maps descriptions + * [Fix] Fix maps locking + * [Fix] Fix max_train setup in ANN module + * [Fix] Fix memory corruption + * [Fix] Fix memory leak in unsigned maps reading + * [Fix] Fix misprints for lto usage + * [Fix] Fix more issues with scripts processing + * [Fix] Fix next-to-last extension length check + * [Fix] Fix openssl initialization + * [Fix] Fix order of arguments + * [Fix] Fix order of initialization + * [Fix] Fix parser + * [Fix] Fix parsing of binary tries + * [Fix] Fix parsing of braced IPv6 addresses + * [Fix] Fix parsing of nested braces in SMTP comments + * [Fix] Fix parsing of rarv5 archives + * [Fix] Fix periodic scheduling when a map is not modified + * [Fix] Fix possible FP in TRACKER_ID rule + * [Fix] Fix post-filters processing + * [Fix] Fix potential NULL dereference + * [Fix] Fix processing of <br> and <hr> tags + * [Fix] Fix processing of addresses in protocol + * [Fix] Fix processing of messages without received headers + * [Fix] Fix proxying issue for unconnected sessions + * [Fix] Fix proxying of the encrypted messages + * [Fix] Fix race condition with shared memory by refcounts + * [Fix] Fix ratelimit initialization + * [Fix] Fix redis set request in replies plugin + * [Fix] Fix redis timeout events handling + * [Fix] Fix redis timeouts processing logic + * [Fix] Fix refcounts in lua_redis + * [Fix] Fix results checking if no master connection is active + * [Fix] Fix return value for couple of lua functions + * [Fix] Fix round-robin selection when upstreams have no weight + * [Fix] Fix rows calculation in graph + * [Fix] Fix rspamd_redis_make_request syntax in replies plugin + * [Fix] Fix scheduling of locked map events + * [Fix] Fix scores detection + * [Fix] Fix searching for newline positions + * [Fix] Fix secure_ip setting in controller + * [Fix] Fix sending data to graph command + * [Fix] Fix setting of score for parts differ + * [Fix] Fix setting of the lua top + * [Fix] Fix setting path for lua + * [Fix] Fix setting path for phishtank + * [Fix] Fix settings application + * [Fix] Fix shm_open call as described in POSIX + * [Fix] Fix size of length in fuzzy mirror wire protocol + * [Fix] Fix smtp grammar issues + * [Fix] Fix some issues with redis API + * [Fix] Fix some issues with retries in the proxy + * [Fix] Fix stack growing + * [Fix] Fix start of body detection in DKIM + * [Fix] Fix state on timeout + * [Fix] Fix stats script + * [Fix] Fix substring search when there are '\0' in strings + * [Fix] Fix symbol name for spf soft fail + * [Fix] Fix symbol type's check + * [Fix] Fix symbols registration and execution + * [Fix] Fix the case of multiple values keys + * [Fix] Fix the default symbol names according to metric + * [Fix] Fix timeout setup on learning + * [Fix] Fix timeouts in redis cache processing + * [Fix] Fix timeouts processing in lua_redis + * [Fix] Fix upstreams interaction for rspamd proxy + * [Fix] Fix usage of rdns reply structure + * [Fix] Fix varargs loop + * [Fix] Fix whitelists and blacklists in SA rules + * [Fix] Fix write servers setup for redis + * [Fix] Fix writing of HTTP messages + * [Fix] Force rspamd to upgrade fuzzy db on opening + * [Fix] Free the correct pointer + * [Fix] Further fixes for lto and static linking + * [Fix] Further fixes for surbl extensions map + * [Fix] Further fixes in maps code + * [Fix] Further improvements to error messages in fuzzy check + * [Fix] Further tweaks to redis garbadge collection + * [Fix] Groups are now case insensitive + * [Fix] Handle log pipe read errors + * [Fix] Handle nested dependencies in SpamAssassin plugin + * [Fix] Implement new automata to skip empty lines for dkim signing + * [Fix] Improve error messages on fuzzy add + * [Fix] Improve lua redis handling + * [Fix] Improve phishing module logging + * [Fix] Improve printing of fuzzy errors + * [Fix] Improve rrd diagnostic errors + * [Fix] Improve strcase hash used in uthash + * [Fix] Include fuzzy key to distinguish storages with different keys + * [Fix] Include slave cluster name into http request + * [Fix] Include some more information about archives + * [Fix] Indicate upstream error on timeout + * [Fix] Initialize hash tables array to avoid crashes + * [Fix] Initialize parser scripts properly + * [Fix] Initialize vars to avoid warnings + * [Fix] Inverse logic for saving ANN + * [Fix] Link lpeg to rspamd lua library + * [Fix] Make extension checks case-insensitive + * [Fix] Mark expired hashes as not found and not as zero flag + * [Fix] Match archive name as well + * [Fix] More and more fixes to redis states + * [Fix] More fixes about shared memory in proxy + * [Fix] More fixes for redis refcounts + * [Fix] More fixes to end of headers detection + * [Fix] More fixes to events logic + * [Fix] More fixes to multi-flag fuzzy storage + * [Fix] More fixes to parts distance calculations + * [Fix] More guards for redis free + * [Fix] One more fix in redis destructor + * [Fix] One more try to fix redis + * [Fix] PIE is required for static build + * [Fix] Partial fix for mmap'd statistics tests + * [Fix] Plug memory leak in proxy + * [Fix] Properly detect end of headers position + * [Fix] Properly init and free session structures + * [Fix] Reduce PRECEDENCE_BULK rule weight + * [Fix] Reduce the default thresholds for learning + * [Fix] Remove Type=forking from systemd unit file (#709) + * [Fix] Remove bad FANN file to save computational resources + * [Fix] Remove event before closing of fd to avoid race conditions + * [Fix] Remove parsing of 'from' variable in redis backend + * [Fix] Remove some bad domains from whitelists + * [Fix] Repair optional dependencies + * [Fix] Reset master connection when retransmitting scan request + * [Fix] Restore ONCE_RECEIVED symbol + * [Fix] Restore compatibility with old lua API behaviour + * [Fix] Restore redis runtime state + * [Fix] Reverse options when received + * [Fix] Send updates to mirrors only if we have some changes + * [Fix] Set host attribute properly when making HTTP request from lua + * [Fix] Set terminated state before calling of async free + * [Fix] Simplify MISSING_MIMEOLE rule + * [Fix] Simplify state machine by ignoring multiple spaces + * [Fix] Skip setting RPATH for static builds + * [Fix] Slightly reduce weights of rules with high FP rate + * [Fix] Some fixes to libmagic initialization + * [Fix] Some more fixes to ratelimit plugin + * [Fix] Strip '\r\n' properly + * [Fix] Switch hashes to mumhash + * [Fix] Treat NaN values properly in graph command + * [Fix] Try to avoid FP when checking for phished URLs + * [Fix] Try to avoid recursive events deletions + * [Fix] Try to fix false positive URL detections in text parts + * [Fix] Try to fix issue in redis stats backend when task is closed + * [Fix] Try to fix proxying of stupid spamc protocol to HTTP mirrors + * [Fix] Try to fix redis crashes + * [Fix] Try to fix upstreams with one element + * [Fix] Try to handle multiline history in a more sane way + * [Fix] Unbreak build on gcc < 4.9 + * [Fix] Update RPM spec/sources (#700) + * [Fix] Update d3evolution version + * [Fix] Update mumhash implementation + * [Fix] Use custom error function for pre and post filters + * [Fix] Use new postfilters and prefilters API in the plugins + * [Fix] Use non-blocking mode for systemd sockets + * [Fix] Use shared memory merely for local backends in the proxy + * [Fix] Use watchers for spf plugin + * [Fix] Varioud fixes to the maps code 1.2.8: - * Another fix for exim workaround (#637) - * Fix unencrypted passwords processing in the controller - * Fix setting path for lua (#652) - * Fix usage of rdns reply structure (#654) - * Use file lock in logger to avoid deadlocks - * Add `application/octet-stream` mime type for `pdf` extension (by @moisseev) - * Implement new automata to skip empty lines for dkim signing (#651) - * Fix parsing of missing classes - * Clarify some rspamc arguments (by @fatalbanana) - * Correct suppress spelling + * Another fix for exim workaround (#637) + * Fix unencrypted passwords processing in the controller + * Fix setting path for lua (#652) + * Fix usage of rdns reply structure (#654) + * Use file lock in logger to avoid deadlocks + * Add `application/octet-stream` mime type for `pdf` extension (by @moisseev) + * Implement new automata to skip empty lines for dkim signing (#651) + * Fix parsing of missing classes + * Clarify some rspamc arguments (by @fatalbanana) + * Correct suppress spelling 1.2.7: - * Slightly reduce weights of rules with high FP rate - * Add workround for rspamd-1.3 - * Fix possible FP in TRACKER_ID rule - * Simplify MISSING_MIMEOLE rule - * Add workaround for gmime CTE stupidity - * Fix mime headers processing - * Fix false positive URL detections in text parts - * Fix Exim shutdown patch - * Enable workaround for exim mailbox format - * Backport shingles static test - * Fix levenshtein distance calculations - * Fix max_train setup in ANN module - * Fix redis structure by adding {NULL, NULL} member - * Fix build with unmodified LibreSSL opensslv.h - * Repair optional dependencies - * Really skip filters in case of pre-result set - * Restore the intended pre-filters behaviour - * Fix ipv6 mask application + * Slightly reduce weights of rules with high FP rate + * Add workround for rspamd-1.3 + * Fix possible FP in TRACKER_ID rule + * Simplify MISSING_MIMEOLE rule + * Add workaround for gmime CTE stupidity + * Fix mime headers processing + * Fix false positive URL detections in text parts + * Fix Exim shutdown patch + * Enable workaround for exim mailbox format + * Backport shingles static test + * Fix levenshtein distance calculations + * Fix max_train setup in ANN module + * Fix redis structure by adding {NULL, NULL} member + * Fix build with unmodified LibreSSL opensslv.h + * Repair optional dependencies + * Really skip filters in case of pre-result set + * Restore the intended pre-filters behaviour + * Fix ipv6 mask application 1.2.6: - * Fix parsing of URLs in texts - * Fix creating of URLs from LUA - * Fix some more URL detector issues - * Fix unit tests - * Fix JIT compilation for PCRE2 expressions - * Fix JIT usage for PCRE2 - * Fix UTF8 mode in PCRE2 - * Add workaround for pre-historic compilers (#605) - * Fix and rescore R_PARTS_DIFFER logic - * Properly set lua paths for tests - * Fix SA rawbody processing - exclude top part - * Store text parts content with newlines stripped - * Properly support SA body regexps - * Fix body rules in SA plugin - * Fix setting of score for parts differ - * More fixes to parts distance calculations - - Use hashed words instead of full words for speed - - Improve levenstein distance calculations and penalise replaces - - Always return number from 0 to 1 - - Use g_malloc instead of alloca - * Fix percents output in R_PARTS_DIFFER - * Plug memory leak in dkim module - * Plug minor memory leak in regexps creation + * Fix parsing of URLs in texts + * Fix creating of URLs from LUA + * Fix some more URL detector issues + * Fix unit tests + * Fix JIT compilation for PCRE2 expressions + * Fix JIT usage for PCRE2 + * Fix UTF8 mode in PCRE2 + * Add workaround for pre-historic compilers (#605) + * Fix and rescore R_PARTS_DIFFER logic + * Properly set lua paths for tests + * Fix SA rawbody processing - exclude top part + * Store text parts content with newlines stripped + * Properly support SA body regexps + * Fix body rules in SA plugin + * Fix setting of score for parts differ + * More fixes to parts distance calculations + - Use hashed words instead of full words for speed + - Improve levenstein distance calculations and penalise replaces + - Always return number from 0 to 1 + - Use g_malloc instead of alloca + * Fix percents output in R_PARTS_DIFFER + * Plug memory leak in dkim module + * Plug minor memory leak in regexps creation 1.2.5: - * Plug an important memory leak in headers getting code - * Remove some bad domains from whitelists + * Plug an important memory leak in headers getting code + * Remove some bad domains from whitelists 1.2.4: - * Implement new multipattern matcher that uses hyperscan if possible - * Use mutlipattern for lua_trie code - * Add utility methods for multipattern - * Use multipattern in url matcher - * Add escape functions for hyperscan - * Allow to optimize lua -> C transition by flattening table args - * Optimize hot paths in SA plugin - * Optimize rspamd_re_cache_type_from_string - * Allow empty tries - * Fix extraction of URLs from Subject - * Allow to have different flags for different patterns in multipattern - * Add common directory for hyperscan cache to config - * Implement caching for hyperscan multipattern - * Attach domain part to `R_SUSPICIOUS_URL` (by @moisseev) - * Allow multipattern scans to be nested for the case of hyperscan - * Simplify SURBL redirector search code and avoid ac_trie - * Add two way substring search algorithm - * Avoid acism usage to find gtube pattern - * Fix processing of empty headers - * Allow to disable pthread mutexes on broken platforms - * Make web interface not send password in query strings (#585) by @fatalbanana - * Add maximum delay to ratelimit module - * Backport fix for empty files inclusion from libucl - * Fix settings id setup - * Add min_learns option to classifiers - * Use more clever to utf8 conversion strategy - * Fix disabling of virtual symbols in the settings - * Rework settings to work properly in metric-less configuration - * Set the default limit for classifier - * Fix ttl based expiration from LRU cache - * Rework DKIM module to use OpenSSL for digests - * Fix mailto urls parsing with hyperscan - * Do not set obscured flag for urls starting with spaces - * Fix crash on redis learn - * Fix ratelimit ctime setting + * Implement new multipattern matcher that uses hyperscan if possible + * Use mutlipattern for lua_trie code + * Add utility methods for multipattern + * Use multipattern in url matcher + * Add escape functions for hyperscan + * Allow to optimize lua -> C transition by flattening table args + * Optimize hot paths in SA plugin + * Optimize rspamd_re_cache_type_from_string + * Allow empty tries + * Fix extraction of URLs from Subject + * Allow to have different flags for different patterns in multipattern + * Add common directory for hyperscan cache to config + * Implement caching for hyperscan multipattern + * Attach domain part to `R_SUSPICIOUS_URL` (by @moisseev) + * Allow multipattern scans to be nested for the case of hyperscan + * Simplify SURBL redirector search code and avoid ac_trie + * Add two way substring search algorithm + * Avoid acism usage to find gtube pattern + * Fix processing of empty headers + * Allow to disable pthread mutexes on broken platforms + * Make web interface not send password in query strings (#585) by @fatalbanana + * Add maximum delay to ratelimit module + * Backport fix for empty files inclusion from libucl + * Fix settings id setup + * Add min_learns option to classifiers + * Use more clever to utf8 conversion strategy + * Fix disabling of virtual symbols in the settings + * Rework settings to work properly in metric-less configuration + * Set the default limit for classifier + * Fix ttl based expiration from LRU cache + * Rework DKIM module to use OpenSSL for digests + * Fix mailto urls parsing with hyperscan + * Do not set obscured flag for urls starting with spaces + * Fix crash on redis learn + * Fix ratelimit ctime setting 1.2.3: - * New DCC module (by @smfreegard) - * Rework whitelist module: - - Now we check different elements for different checks - - MIME from for DMARC - - DKIM signature domain for DKIM - - SMTP from or HELO for SPF - * Fix regexps results combination (*critical*) - * Fix issue with expressions processing (*critical*) - * Optimize strlcpy for aligned input - * Add support of half-closed connection in lua_tcp - * Allow to print compact json in client - * Save required score in history (#581) - * Allow to attach file descriptors to control commands - * Allow to send descriptors from workers to main - * Allow to attach fd when broadcasting to workers - * Implement log pipe feature for rspamd logs analysis - * Add `log_helper` worker - * Add `URIBL_SBL_CSS` (by @smfreegard) - * Add worker scripts functionality - * Add on load hooks for rspamd_config - * Add lua scripts for log_helper worker - * Add generic maillist detector (#584) - * Implement FANN autolearn using log_helper worker - * Rework metrics configuration to allow includes - * Change default value of forced removal in composite rules - * Allow to use assembly version of blake2b on x86 cpu - * Use less precise (but faster) clock if possible - * Insert redirected URL to the urls list - * Allow to get and set callback data for rspamd symbols - * Add binary heap implementation - * Use binary heap for expire algorithms in the hash - * Use `least frequent used` expiration strategy - * Allow to get mime headers from a task - * Add support for mime headers in `regexp` module - * Update Exim patches (by @fatalbanana) - * Allow building rspamd with jemalloc - * Save multipart boundaries - * SA plugin changes: - - Properly handle MIME headers - - Fix eval:check_for_missing_to_header rule - - Implement SA compatible body regexps - - Use sabody rules in SA plugin - * LUA API changes: - - Add util.get_ticks function - - Add util.stat function - - Add task:get_symbols_numeric method - - Add method to get number of symbols in the cache - - Add lua methods to get redirected urls - - Allow to get callbacks for lua symbols - - Add config:set_symbol_callback function + * New DCC module (by @smfreegard) + * Rework whitelist module: + - Now we check different elements for different checks + - MIME from for DMARC + - DKIM signature domain for DKIM + - SMTP from or HELO for SPF + * Fix regexps results combination (*critical*) + * Fix issue with expressions processing (*critical*) + * Optimize strlcpy for aligned input + * Add support of half-closed connection in lua_tcp + * Allow to print compact json in client + * Save required score in history (#581) + * Allow to attach file descriptors to control commands + * Allow to send descriptors from workers to main + * Allow to attach fd when broadcasting to workers + * Implement log pipe feature for rspamd logs analysis + * Add `log_helper` worker + * Add `URIBL_SBL_CSS` (by @smfreegard) + * Add worker scripts functionality + * Add on load hooks for rspamd_config + * Add lua scripts for log_helper worker + * Add generic maillist detector (#584) + * Implement FANN autolearn using log_helper worker + * Rework metrics configuration to allow includes + * Change default value of forced removal in composite rules + * Allow to use assembly version of blake2b on x86 cpu + * Use less precise (but faster) clock if possible + * Insert redirected URL to the urls list + * Allow to get and set callback data for rspamd symbols + * Add binary heap implementation + * Use binary heap for expire algorithms in the hash + * Use `least frequent used` expiration strategy + * Allow to get mime headers from a task + * Add support for mime headers in `regexp` module + * Update Exim patches (by @fatalbanana) + * Allow building rspamd with jemalloc + * Save multipart boundaries + * SA plugin changes: + - Properly handle MIME headers + - Fix eval:check_for_missing_to_header rule + - Implement SA compatible body regexps + - Use sabody rules in SA plugin + * LUA API changes: + - Add util.get_ticks function + - Add util.stat function + - Add task:get_symbols_numeric method + - Add method to get number of symbols in the cache + - Add lua methods to get redirected urls + - Allow to get callbacks for lua symbols + - Add config:set_symbol_callback function 1.2.2: - * Use HTTP Content-Type on non mime input if possible - * Save log level when compressing log messages - * Further rework of composite rules (add '^' prefix) - * Add tracking for rspamd expressions - * Store actions limits in metric result - * Fix parsing of include/redirect with many records in SPF - * Add method to disable symbols execution in the cache - * Allow to disable checks from settings - * Allow to select settings by id in HTTP query - * Find URLs with '\r' and '\n' inside href attribute - * Implement vectored mode for hyperscan (experimental) - * Improve client connection errors diagnostics - * Allow to edit new files with signtool - * Improve hashes performance on 32 bit platforms - * Fix sorting of limits - * Remove slow and unused rules `INVALID_EXIM_RECEIVED*` - * Add expression:process_traced lua method - * Allow tables in task:insert_result - * Save trace for SA metas - * Do not parse broken TLD parts in URLs - * Investigate many border cases in URLs parser + * Use HTTP Content-Type on non mime input if possible + * Save log level when compressing log messages + * Further rework of composite rules (add '^' prefix) + * Add tracking for rspamd expressions + * Store actions limits in metric result + * Fix parsing of include/redirect with many records in SPF + * Add method to disable symbols execution in the cache + * Allow to disable checks from settings + * Allow to select settings by id in HTTP query + * Find URLs with '\r' and '\n' inside href attribute + * Implement vectored mode for hyperscan (experimental) + * Improve client connection errors diagnostics + * Allow to edit new files with signtool + * Improve hashes performance on 32 bit platforms + * Fix sorting of limits + * Remove slow and unused rules `INVALID_EXIM_RECEIVED*` + * Add expression:process_traced lua method + * Allow tables in task:insert_result + * Save trace for SA metas + * Do not parse broken TLD parts in URLs + * Investigate many border cases in URLs parser 1.2.1: - * Add list support to `mime types` module configuration (by @moisseev) - * Allow symbols params to be printed in logs - * Fix `MIME_BAD_ATTACHMENT` false positives for MDN/DSN - * Fix crashes on arm32 - * Do not classify message if some class is missing - * Fix cryptobox cleanup - * Remove multipart/report from bad mime types (#569) - * Improve logging for fuzzy hashes - * Show map URLs in webui - * Sort symbols in webui + * Add list support to `mime types` module configuration (by @moisseev) + * Allow symbols params to be printed in logs + * Fix `MIME_BAD_ATTACHMENT` false positives for MDN/DSN + * Fix crashes on arm32 + * Do not classify message if some class is missing + * Fix cryptobox cleanup + * Remove multipart/report from bad mime types (#569) + * Improve logging for fuzzy hashes + * Show map URLs in webui + * Sort symbols in webui 1.2.0: - * New dynamic updates plugin - * Regular expressions map support - * Faster radix trie algorithm - * Faster siphash for AVX2 supporing CPUs (used in fuzzy hashes) - * PCRE2 support - * Allow quoted and slashed keys in map - * Add proper support of DNS resolvers balancing (#552) - * Rework includes and configuration system for better local changes support - * New keypairs framework for signing and encryption - * Added support for dynamic modules and workers - * Allow to dump configuration with help comments - * Rework once_received module - - Fix priority for `good_hosts` - - If a good host has been found do not add once_received symbols - - Fix priorities for strict once_received - - Add ability to whitelist IP addresses - * Implement support of signed maps for HTTP and file maps - * Add command to sync fuzzy storage (#533) - * Rework system of symbols and actions registration - It is possible now to use priorities when adding symbols to metrics and - override scores for symbols with lower priority with the scores with - high priority. - * Add auth support and db selection for redis stats - * Improve composite rules application - * Add ignore_received option - * Fix critical issue with inconsistent resorting - * Fix `all` in spf redirects - * Add punycoded versions for IDN domains (#554) - * Improve sorting order for symbols cache - * Add lockless logging for processes management - * Allow to specify flags for metric symbols - * Load images height and width from style attribute (#538) - * Override DNS requests limits for SPF and DKIM - * Fix resetting symbols to their default values in WebUI - * Improve configuration agility for redis stats - * Allow to set db and password for redis in stat_convert - * Import the latest libucl - * LUA API changes: - - Add rspamd_version function to LUA API - - Add lua_cryptobox module - - Add lua_map module - - Add task:set_metric_action lua API method - - Fix race condition in lua_tcp module - - Fix a lot of issues in lua_redis module - - Rework and abstract lua maps API - - Add util.strlen_utf8 lua function - - Add lua functions for caseless comparison - - Allow optional symbols registration - - Add config:add_map table form method, add regexp maps - - Add task:has_urls method - - Add task:has_flag method - - Add html tags methods to lua_html - - Add task:get_dns_req - * Plugins changes: - - Add support for WLBLEval SA plugin - - Use caseless comparison in SA and DMARC plugins - - Allow SA plugin to set scores for rspamd symbols - - Add regexp maps support to multimap - - Allow filenames match in multimap - - Add more filters for the existing map types - - Fix html images rules to reduce FP rates - * New rules: - - LONG_SUBJ - too long subject - - MIME_BAD_ATTACHMENT - bad attachment type - - RDNS_NONE - no reverse DNS record for sender's IP - - Fix MISSING_MIMEOLE rule for modern OE - * Many other bugfixes, memory leaks plugs thanks to: - - Coverity scan - - New gcc-6 warnings - - valgrind manual iterations - * Documentation improvements: - - FAQ list: https://rspamd.com/doc/faq.html - - Reworked quick start guide - - Added documentation for all active modules - * Other changes: - - Dropped Ubuntu Vivid support - - Added Ubuntu Xenial support - - Rework build system for rspamd and rmilter + * New dynamic updates plugin + * Regular expressions map support + * Faster radix trie algorithm + * Faster siphash for AVX2 supporing CPUs (used in fuzzy hashes) + * PCRE2 support + * Allow quoted and slashed keys in map + * Add proper support of DNS resolvers balancing (#552) + * Rework includes and configuration system for better local changes support + * New keypairs framework for signing and encryption + * Added support for dynamic modules and workers + * Allow to dump configuration with help comments + * Rework once_received module + - Fix priority for `good_hosts` + - If a good host has been found do not add once_received symbols + - Fix priorities for strict once_received + - Add ability to whitelist IP addresses + * Implement support of signed maps for HTTP and file maps + * Add command to sync fuzzy storage (#533) + * Rework system of symbols and actions registration + It is possible now to use priorities when adding symbols to metrics and + override scores for symbols with lower priority with the scores with + high priority. + * Add auth support and db selection for redis stats + * Improve composite rules application + * Add ignore_received option + * Fix critical issue with inconsistent resorting + * Fix `all` in spf redirects + * Add punycoded versions for IDN domains (#554) + * Improve sorting order for symbols cache + * Add lockless logging for processes management + * Allow to specify flags for metric symbols + * Load images height and width from style attribute (#538) + * Override DNS requests limits for SPF and DKIM + * Fix resetting symbols to their default values in WebUI + * Improve configuration agility for redis stats + * Allow to set db and password for redis in stat_convert + * Import the latest libucl + * LUA API changes: + - Add rspamd_version function to LUA API + - Add lua_cryptobox module + - Add lua_map module + - Add task:set_metric_action lua API method + - Fix race condition in lua_tcp module + - Fix a lot of issues in lua_redis module + - Rework and abstract lua maps API + - Add util.strlen_utf8 lua function + - Add lua functions for caseless comparison + - Allow optional symbols registration + - Add config:add_map table form method, add regexp maps + - Add task:has_urls method + - Add task:has_flag method + - Add html tags methods to lua_html + - Add task:get_dns_req + * Plugins changes: + - Add support for WLBLEval SA plugin + - Use caseless comparison in SA and DMARC plugins + - Allow SA plugin to set scores for rspamd symbols + - Add regexp maps support to multimap + - Allow filenames match in multimap + - Add more filters for the existing map types + - Fix html images rules to reduce FP rates + * New rules: + - LONG_SUBJ - too long subject + - MIME_BAD_ATTACHMENT - bad attachment type + - RDNS_NONE - no reverse DNS record for sender's IP + - Fix MISSING_MIMEOLE rule for modern OE + * Many other bugfixes, memory leaks plugs thanks to: + - Coverity scan + - New gcc-6 warnings + - valgrind manual iterations + * Documentation improvements: + - FAQ list: https://rspamd.com/doc/faq.html + - Reworked quick start guide + - Added documentation for all active modules + * Other changes: + - Dropped Ubuntu Vivid support + - Added Ubuntu Xenial support + - Rework build system for rspamd and rmilter 1.1.4: - * Print traceback on lua errors in lua config - * Fix leaks in lua error paths - * Improve 'R_EMPTY_IMAGE' rule - * Fix metas memoization in SA plugin - * Properly set `flag` in fuzzy replies - * Fix arguments order - * Fix issue with out-of-boundary reading - * Fix issues found by coverity - * Same result checking error found by coverity - * Fix varargs processing (found by coverity) - * Fix error in printing hex - * Reduce weights for some hfilter patterns - * Add aliases for task:get_from_ip: - - task:get_addr - - task:get_from_addr - - task:get_ip - * Rework once_received module - - Fix priority for `good_hosts` - - If a good host has been found do not add once_received symbols - - Fix priorities for strict once_received - - Add ability to whitelist IP addresses - * Fix `MISSING_MIMEOLE` rule for modern OE - * Treat meta tags as embedded tags (#501) + * Print traceback on lua errors in lua config + * Fix leaks in lua error paths + * Improve 'R_EMPTY_IMAGE' rule + * Fix metas memoization in SA plugin + * Properly set `flag` in fuzzy replies + * Fix arguments order + * Fix issue with out-of-boundary reading + * Fix issues found by coverity + * Same result checking error found by coverity + * Fix varargs processing (found by coverity) + * Fix error in printing hex + * Reduce weights for some hfilter patterns + * Add aliases for task:get_from_ip: + - task:get_addr + - task:get_from_addr + - task:get_ip + * Rework once_received module + - Fix priority for `good_hosts` + - If a good host has been found do not add once_received symbols + - Fix priorities for strict once_received + - Add ability to whitelist IP addresses + * Fix `MISSING_MIMEOLE` rule for modern OE + * Treat meta tags as embedded tags (#501) 1.1.3: - * Fix DSN rules when SMTP from is unavailable - * Fix statconvert routine to avoid lua module usage - * Set a sane quark for configtest to avoid NULL to be printed in logs - * Support c11 if available - * Fix parsing of ip:port strings - * Add more diagnostic for lua subr errors - * Fix task:set_from_ip lua method - * Add basic routines for digital signatures - * Add tool for digital signatures - * Add plain open file API method for atomic open - * Fix parsing nested braces inside logger vars - * Pre filters now actually skip processing - * Add pre-filter mode for multimap - * Switch to apache 2 license + * Fix DSN rules when SMTP from is unavailable + * Fix statconvert routine to avoid lua module usage + * Set a sane quark for configtest to avoid NULL to be printed in logs + * Support c11 if available + * Fix parsing of ip:port strings + * Add more diagnostic for lua subr errors + * Fix task:set_from_ip lua method + * Add basic routines for digital signatures + * Add tool for digital signatures + * Add plain open file API method for atomic open + * Fix parsing nested braces inside logger vars + * Pre filters now actually skip processing + * Add pre-filter mode for multimap + * Switch to apache 2 license 1.1.2: - * Fix stat_cache closing - * Add checkpoints to sqlite3 learn cache - * Do not recompile lua generated headers all the time - * Increase number of messages learned - * Fix issues with dual stack and hfilter - * Disable MID checks for hfilter by default - * Fix cache definitions in multiple classifier and no type - * Don't crash if learn cache failed to initialize - * Fix googlegroups support in maillist plugin - * Rework flags LUA API: - - Allow to check for a specific flag - - Add `learn_spam`, `learn_ham` and `broken_headers` flags - - Unify internal functions - * Add `BROKEN_HEADERS` rule - * Add support for forged confirmation headers (by @AdUser) - * Allow `any`, `mime` and `smtp` for get_from/get_recipients - * Add mime types checking plugin - * Add rule to detect spammers attempts to cheat mime parsing - * Rework parsing of IP addresses in configuration (better IPv6 support) - * Add `util.parse_mail_address` function to LUA API - * Add lua sqlite3 module - * Implement synchronous redis call - * Ratelimit: avoid possible indexing of nil value (Fixes #498) (by @fatalbanana) - * Add stat_convert command to convert stats tokens from sqlite3 to redis - * Implement redis advanced lua api with pipelining - * Fix memory leak on redis stat (#500) - * Fix user/language learn count in sqlite statistics (#496) (by @fatalbanana) - * Fix build with custom pcre - * Fix fuzzy relearning (#498) - * Improve planning of asynchronous tasks - * Show slow rules in log - * Add warning for slow regexps - * Add base32 decode/encode routines to lua util - * Allow converting of learn cache from sqlite to redis - * Add methods to check if a messages has from/rcpts - * Improve and fix multimap plugin: - - Restore 'header' maps - - Add filters for headers - - Add 'email:addr', 'email:user', 'email:domain' and 'email:name' filters - - Add generic regexp filters - * Disable reload command in rc scripts - * Improve runtime CPU dispatcher for libcryptobox - * Add preliminary support of digital signatures via ed25519 - * Add detection for RDRAND support - * Print configuration of crypto on start - * A in SPF presumes AAAA lookup as well + * Fix stat_cache closing + * Add checkpoints to sqlite3 learn cache + * Do not recompile lua generated headers all the time + * Increase number of messages learned + * Fix issues with dual stack and hfilter + * Disable MID checks for hfilter by default + * Fix cache definitions in multiple classifier and no type + * Don't crash if learn cache failed to initialize + * Fix googlegroups support in maillist plugin + * Rework flags LUA API: + - Allow to check for a specific flag + - Add `learn_spam`, `learn_ham` and `broken_headers` flags + - Unify internal functions + * Add `BROKEN_HEADERS` rule + * Add support for forged confirmation headers (by @AdUser) + * Allow `any`, `mime` and `smtp` for get_from/get_recipients + * Add mime types checking plugin + * Add rule to detect spammers attempts to cheat mime parsing + * Rework parsing of IP addresses in configuration (better IPv6 support) + * Add `util.parse_mail_address` function to LUA API + * Add lua sqlite3 module + * Implement synchronous redis call + * Ratelimit: avoid possible indexing of nil value (Fixes #498) (by @fatalbanana) + * Add stat_convert command to convert stats tokens from sqlite3 to redis + * Implement redis advanced lua api with pipelining + * Fix memory leak on redis stat (#500) + * Fix user/language learn count in sqlite statistics (#496) (by @fatalbanana) + * Fix build with custom pcre + * Fix fuzzy relearning (#498) + * Improve planning of asynchronous tasks + * Show slow rules in log + * Add warning for slow regexps + * Add base32 decode/encode routines to lua util + * Allow converting of learn cache from sqlite to redis + * Add methods to check if a messages has from/rcpts + * Improve and fix multimap plugin: + - Restore 'header' maps + - Add filters for headers + - Add 'email:addr', 'email:user', 'email:domain' and 'email:name' filters + - Add generic regexp filters + * Disable reload command in rc scripts + * Improve runtime CPU dispatcher for libcryptobox + * Add preliminary support of digital signatures via ed25519 + * Add detection for RDRAND support + * Print configuration of crypto on start + * A in SPF presumes AAAA lookup as well 1.1.1: - * Fix duplicated XBL symbol - * Reduce log severity for ratelimit missing servers - * Fix XBL composite to avoid duplicate symbols - * Reduce weight of URL_ONLY rule due to FP rate - * Disable fuzzy hashes from the metadata for now - * Fix processing of empty messages (#486) - * Always treat DNS timeouts as temporary fail for SPF - * Fix issue with SPF double IP stack (#483) - * Use X-Forwarded-For when checking secure_ip (#488) - * Fix hash calculation for sqlite stats - * Fix memory corruption on punycode - * Fix strings allocation in punycode - * Fix error message (#491) + * Fix duplicated XBL symbol + * Reduce log severity for ratelimit missing servers + * Fix XBL composite to avoid duplicate symbols + * Reduce weight of URL_ONLY rule due to FP rate + * Disable fuzzy hashes from the metadata for now + * Fix processing of empty messages (#486) + * Always treat DNS timeouts as temporary fail for SPF + * Fix issue with SPF double IP stack (#483) + * Use X-Forwarded-For when checking secure_ip (#488) + * Fix hash calculation for sqlite stats + * Fix memory corruption on punycode + * Fix strings allocation in punycode + * Fix error message (#491) 1.1.0: - * Incompatible change: sqlite3 and per_user behaviour: - Now both redis and sqlite3 follows the common principles for per-user - statistics: - 1) If per-user statistics is enabled check per-user tokens ONLY - 2) If per-user statistics is not enabled then check common tokens ONLY - If you need old behaviour, then you'd need to use separate classifier - for per-user statistics. - * Implement redis statistics backend and cache - * Implement autolearning for statistics - * Reworked statistics architecture from scratch - * Add hyperscan (https://github.com/01org/hyperscan) engine for regular - expressions: - - add lazy loader for hyperscan databases - - rework regexp cache to have joint pcre/hyperscan scanning - - implement hyperscan pre-filter support - - add compilation guards for bad expressions - - implement `rspamadm control recompile` command - - implement hyperscan cache monitoring - - slides: <https://highsecure.ru/rspamd-hyperscan.pdf> - * Implement flexible task logging - * Rework fuzzy worker: - - it is now possible to run multiple fuzzy workers; - - implement lazy writing as sqlite3 is bad at concurrent writing; - - add retries for simple sql commands in fuzzy backend; - - use fine-grained transactions for fuzzy; - - implement new multi-pubkeys mode; - - allow encrypted only storages; - - rework statistics for fuzzy; - - add `rspamadm control fuzzystat` command for extended statistics; - - implement human readable output for the previous command; - - add condition script for learning fuzzy storage; - * Various fixes to SPF: - - fix `redirect` records; - - fix domains when parsing mx/ptr/a records in includes/redirects; - - fix issues with multiple addresses in SPF records; - - ignore SPF results in case of DNS failure; - - adjust TTL of records when resolving subelements of SPF records; - - always select `v=spf1` line if it is available - - do not cache records with DNS failure in subrequests; - - ignore records with temporary fails during subrequests resolving; - - fix `RDNS_RC_NOREC` support; - * Add clang plugin for static analysis: - - implement static checks for `rspamd_printf` format strings; - * Add 'allow_raw_input' option for non-mime messages - * Recognize types using libmagic - * Fix parsing of IPv6 received headers - * Add new interface of communication between workers in rspamd - * Add support for named socketpairs - * Don't write URLs by default as it is too verbose - * Set status for HTTP replies - * Try load `rspamd.conf.override` - * Implement words decaying for text parts to limit many checks - * Improve support of SA rules and plugins: - - add check_for_shifted_date and check_for_missing_to_header eval rules; - - add 'check_relays_unparseable' support; - - add `check_for_mime('mime_attachement')` function; - - use new re_cache interface for all SA rules; - - add support for `Mail::SpamAssassin::Plugin::MIMEHeader`; - - add support of 'special' SA headers to `exists` function; - - fix issue when SA metas contain other metas; - - fix freemail rules; - * Many fixes to the URL parser - * Match any newline character in regexps - * Fix resolving of upstreams and detection of poor IPv6 configurations - * Parse upstreams selection algorithm from the configuration line - * Add `reresolve` command to the control interface - * Generate fuzzy hashes from task metadata (URLs and headers) - * Add method to check if IP is local and `local_addrs` option - * Implement forced timeout for delayed filters - * Disable fast path of pcre-jit as it seems to be broken - * Bayes fixes: - - new normalizer function; - - really use weights of tokens from the OSB algorithm; - - restore multiple classifiers support; - * Rules changes: - - add `R_SUSPICIOUS_URL` rule that detects obfuscated URL's; - - improve empty image rule; - - rework `FORGED_RECIPIENTS` rule; - - reduce weight of `SUSPICIOUS_RECIPS`; - - fix `*_NORESOLVE_MX` symbols in hfilter; - - add `SUBJ_ALL_CAPS` rule with support of UTF8 - - add spamhaus SBL to uribl - - fix `SUSPICIOUS_RECIPS` and `SORTED_RECIPS` rules - - remove `R_TO_SEEMS_AUTO` as it generates a lot of FP; - - add new Message-ID regexp for Thunderbird (by @moisseev); - * Plugins changes: - - allow ratelimit plugin to set symbol instead of pre-result - - support IP DNS black lists for URIBL (e.g spamhaus SBL); - - drop deprecated SURBL bits (by @fatalbanana) - - rename `JP_SURBL_MULTI` to `ABUSE_SURBL` (by @fatalbanana) - - add `SURBL_BLOCKED` (by @fatalbanana) - - add `CR_SURBL` - - SURBL: allow fallthrough to default symbol (by @fatalbanana) - - Settings: fix IP match (by @fatalbanana) - - SURBL: add missing symbols to metric (by @fatalbanana) - - allow processing images urls for SURBL - - unconditionally disable SPF for authenticated users and local networks - * Rework ratelimit plugin - - switch to `rates` instead of old and stupid strings to setup; - - check if a bucket is zero and disable the corresponding limits' - - turn off all buckets by default; - - check either `rcpt` or `user` buckets, not all together' - - document new `rates` and `symbol` options; - - inform user about what buckets are used in the configuration; - * Add neural network **experimental** plugin - * Add a sample script to learn neural network from rspamd logs - * Add documentation strings support to rspamd: - - add strings for the main configuration options; - - document workers options; - - add internal plugin options; - - create `rspamadm confighelp` routine; - - implement human readable output for the previous command; - - add subtree search support; - - add keyword search support; - * Documentation improvements, tutorials section, statistics description - * Many other minor and major bugfixes not noted here + * Incompatible change: sqlite3 and per_user behaviour: + Now both redis and sqlite3 follows the common principles for per-user + statistics: + 1) If per-user statistics is enabled check per-user tokens ONLY + 2) If per-user statistics is not enabled then check common tokens ONLY + If you need old behaviour, then you'd need to use separate classifier + for per-user statistics. + * Implement redis statistics backend and cache + * Implement autolearning for statistics + * Reworked statistics architecture from scratch + * Add hyperscan (https://github.com/01org/hyperscan) engine for regular + expressions: + - add lazy loader for hyperscan databases + - rework regexp cache to have joint pcre/hyperscan scanning + - implement hyperscan pre-filter support + - add compilation guards for bad expressions + - implement `rspamadm control recompile` command + - implement hyperscan cache monitoring + - slides: <https://highsecure.ru/rspamd-hyperscan.pdf> + * Implement flexible task logging + * Rework fuzzy worker: + - it is now possible to run multiple fuzzy workers; + - implement lazy writing as sqlite3 is bad at concurrent writing; + - add retries for simple sql commands in fuzzy backend; + - use fine-grained transactions for fuzzy; + - implement new multi-pubkeys mode; + - allow encrypted only storages; + - rework statistics for fuzzy; + - add `rspamadm control fuzzystat` command for extended statistics; + - implement human readable output for the previous command; + - add condition script for learning fuzzy storage; + * Various fixes to SPF: + - fix `redirect` records; + - fix domains when parsing mx/ptr/a records in includes/redirects; + - fix issues with multiple addresses in SPF records; + - ignore SPF results in case of DNS failure; + - adjust TTL of records when resolving subelements of SPF records; + - always select `v=spf1` line if it is available + - do not cache records with DNS failure in subrequests; + - ignore records with temporary fails during subrequests resolving; + - fix `RDNS_RC_NOREC` support; + * Add clang plugin for static analysis: + - implement static checks for `rspamd_printf` format strings; + * Add 'allow_raw_input' option for non-mime messages + * Recognize types using libmagic + * Fix parsing of IPv6 received headers + * Add new interface of communication between workers in rspamd + * Add support for named socketpairs + * Don't write URLs by default as it is too verbose + * Set status for HTTP replies + * Try load `rspamd.conf.override` + * Implement words decaying for text parts to limit many checks + * Improve support of SA rules and plugins: + - add check_for_shifted_date and check_for_missing_to_header eval rules; + - add 'check_relays_unparseable' support; + - add `check_for_mime('mime_attachement')` function; + - use new re_cache interface for all SA rules; + - add support for `Mail::SpamAssassin::Plugin::MIMEHeader`; + - add support of 'special' SA headers to `exists` function; + - fix issue when SA metas contain other metas; + - fix freemail rules; + * Many fixes to the URL parser + * Match any newline character in regexps + * Fix resolving of upstreams and detection of poor IPv6 configurations + * Parse upstreams selection algorithm from the configuration line + * Add `reresolve` command to the control interface + * Generate fuzzy hashes from task metadata (URLs and headers) + * Add method to check if IP is local and `local_addrs` option + * Implement forced timeout for delayed filters + * Disable fast path of pcre-jit as it seems to be broken + * Bayes fixes: + - new normalizer function; + - really use weights of tokens from the OSB algorithm; + - restore multiple classifiers support; + * Rules changes: + - add `R_SUSPICIOUS_URL` rule that detects obfuscated URL's; + - improve empty image rule; + - rework `FORGED_RECIPIENTS` rule; + - reduce weight of `SUSPICIOUS_RECIPS`; + - fix `*_NORESOLVE_MX` symbols in hfilter; + - add `SUBJ_ALL_CAPS` rule with support of UTF8 + - add spamhaus SBL to uribl + - fix `SUSPICIOUS_RECIPS` and `SORTED_RECIPS` rules + - remove `R_TO_SEEMS_AUTO` as it generates a lot of FP; + - add new Message-ID regexp for Thunderbird (by @moisseev); + * Plugins changes: + - allow ratelimit plugin to set symbol instead of pre-result + - support IP DNS black lists for URIBL (e.g spamhaus SBL); + - drop deprecated SURBL bits (by @fatalbanana) + - rename `JP_SURBL_MULTI` to `ABUSE_SURBL` (by @fatalbanana) + - add `SURBL_BLOCKED` (by @fatalbanana) + - add `CR_SURBL` + - SURBL: allow fallthrough to default symbol (by @fatalbanana) + - Settings: fix IP match (by @fatalbanana) + - SURBL: add missing symbols to metric (by @fatalbanana) + - allow processing images urls for SURBL + - unconditionally disable SPF for authenticated users and local networks + * Rework ratelimit plugin + - switch to `rates` instead of old and stupid strings to setup; + - check if a bucket is zero and disable the corresponding limits' + - turn off all buckets by default; + - check either `rcpt` or `user` buckets, not all together' + - document new `rates` and `symbol` options; + - inform user about what buckets are used in the configuration; + * Add neural network **experimental** plugin + * Add a sample script to learn neural network from rspamd logs + * Add documentation strings support to rspamd: + - add strings for the main configuration options; + - document workers options; + - add internal plugin options; + - create `rspamadm confighelp` routine; + - implement human readable output for the previous command; + - add subtree search support; + - add keyword search support; + * Documentation improvements, tutorials section, statistics description + * Many other minor and major bugfixes not noted here 1.0.11: - * Fix spf redirects - * Fix domains when parsing mx/ptr/a records in includes/redirects - * Fix unfolded base64 encoding - * Fix GError use-after-free - * Do not rewrite the original url when using redirector - * Fix parsing of fragment in urls - * Fix processing of HTML tags - * Improve empty image rule - * Avoid long double type - * Fix tokens weights in OSB algorithm - * Improve debugging for bayes + * Fix spf redirects + * Fix domains when parsing mx/ptr/a records in includes/redirects + * Fix unfolded base64 encoding + * Fix GError use-after-free + * Do not rewrite the original url when using redirector + * Fix parsing of fragment in urls + * Fix processing of HTML tags + * Improve empty image rule + * Avoid long double type + * Fix tokens weights in OSB algorithm + * Improve debugging for bayes 1.0.10: - * Fix settings application (#416) - * Fix another issue with fixed strings - * Fix hash function invocation - * Use the proper string for make_dns_request in lua_http - * Fix scan time output - * Update webui: - - fix labels for greylisting - - fix dimension of scan time + * Fix settings application (#416) + * Fix another issue with fixed strings + * Fix hash function invocation + * Use the proper string for make_dns_request in lua_http + * Fix scan time output + * Update webui: + - fix labels for greylisting + - fix dimension of scan time 1.0.9: - * Emergency fix in keyed blake2 to fix fuzzy hashes and encrypted password - * Support passwords longer than 64 symbols + * Emergency fix in keyed blake2 to fix fuzzy hashes and encrypted password + * Support passwords longer than 64 symbols 1.0.8: - * Add function to traverse AST atoms - * Allow dependencies on rspamd symbols for SA metas - * Fix memory corruption when timeout is removed in fuzzy check - * Fix encrypted fuzzy add processing - * Avoid use-after-free in controller session destructor - * Use session pool instead of task pool in fuzzy check - * Fix assembly in i386 mode (#413, #412) + * Add function to traverse AST atoms + * Allow dependencies on rspamd symbols for SA metas + * Fix memory corruption when timeout is removed in fuzzy check + * Fix encrypted fuzzy add processing + * Avoid use-after-free in controller session destructor + * Use session pool instead of task pool in fuzzy check + * Fix assembly in i386 mode (#413, #412) 1.0.7: - * Plugged memory leaks in internet address object & html parser - * Fixed static build - * Fixed multiple sigchld processing - * Fixed deletion of signal events after event processing loop - * Fixed build on ARM (#404 - reported by @Gottox) - * Fixed setting the default mask for SPF. - * Fixed sanitisation of HTTP query values - * Fixed parsing of the last header in encrypted HTTP messages - * Additions and fixes for test suite & benchmarks - * Added openssl aes-256-gcm support to libcryptobox & HTTP server - * Implemented support for starting multiple HTTP servers - * Implemented batch accept in HTTP server - * Added module to get data from HTTP headers (#285 - reported by @msimerson) - * Added `rspamadm control` command - * Added ability to sort counters output. - * Added ability to specify custom headers for rspamc client - * Fix architecture detection - * Converted history storage to the UCL format - * Allow flexible number of rows in history - * Fix action badges in WebUI - * Add universal cryptobox hash API - * Migrated to the optimized blake2b implementation adopted from Andrew Moon - * Allow explicit loading of specific modules - * Always load settings module - * Allow to add symbols from settings - * Fix double free in the controller fuzzy learn command - * Avoid endless loop when cannot open sqlite db - * Updated libucl + * Plugged memory leaks in internet address object & html parser + * Fixed static build + * Fixed multiple sigchld processing + * Fixed deletion of signal events after event processing loop + * Fixed build on ARM (#404 - reported by @Gottox) + * Fixed setting the default mask for SPF. + * Fixed sanitisation of HTTP query values + * Fixed parsing of the last header in encrypted HTTP messages + * Additions and fixes for test suite & benchmarks + * Added openssl aes-256-gcm support to libcryptobox & HTTP server + * Implemented support for starting multiple HTTP servers + * Implemented batch accept in HTTP server + * Added module to get data from HTTP headers (#285 - reported by @msimerson) + * Added `rspamadm control` command + * Added ability to sort counters output. + * Added ability to specify custom headers for rspamc client + * Fix architecture detection + * Converted history storage to the UCL format + * Allow flexible number of rows in history + * Fix action badges in WebUI + * Add universal cryptobox hash API + * Migrated to the optimized blake2b implementation adopted from Andrew Moon + * Allow explicit loading of specific modules + * Always load settings module + * Allow to add symbols from settings + * Fix double free in the controller fuzzy learn command + * Avoid endless loop when cannot open sqlite db + * Updated libucl 1.0.6: - * Fix build on i386 - * Update CentOS7 service file patch (by @fatalbanana) - * Fix path to rspamadm in Debian init script (by @fatalbanana) - * Fix broken '_SC_GETPW_R_SIZE_MAX' on FreeBSD - * Fix portability issues - * Use cryptobox chacha for libottery - * Better support of 32 bit builds - * Fix header name tokens setup - * Fix levenstein distance method for words - * Add workaround for old libevent (#400) - * Fix microseconds in termination timer - * Fix some more issues with fixed strings - * Explicitly test CPU instructions even after CPUID call - * Do not check out of boundary memory - * Do not output broken emails - * Fix unknown symbols registration - * Handle SIGILL using longjmp - * Block signals when exiting event loop - * Fix incorrect allocation size - * Slightly optimize alignment - * Restore rspamd -t for compatibility - * Add more sanity checks for emails + * Fix build on i386 + * Update CentOS7 service file patch (by @fatalbanana) + * Fix path to rspamadm in Debian init script (by @fatalbanana) + * Fix broken '_SC_GETPW_R_SIZE_MAX' on FreeBSD + * Fix portability issues + * Use cryptobox chacha for libottery + * Better support of 32 bit builds + * Fix header name tokens setup + * Fix levenstein distance method for words + * Add workaround for old libevent (#400) + * Fix microseconds in termination timer + * Fix some more issues with fixed strings + * Explicitly test CPU instructions even after CPUID call + * Do not check out of boundary memory + * Do not output broken emails + * Fix unknown symbols registration + * Handle SIGILL using longjmp + * Block signals when exiting event loop + * Fix incorrect allocation size + * Slightly optimize alignment + * Restore rspamd -t for compatibility + * Add more sanity checks for emails 1.0.5: - * Add rspamd control interface: - - support `stat` command to get runtime stats of rspamd workers - - support `reload` command to reload runtime elements (e.g. sqlite3 databases) - * Rework curve25519 library for modular design: - - add Sandy2x implementation by Tung Chou - - fix CPU detection for variables loading assembly - - add testing for curve25519 ECDH - * New fixed strings library - * Add `R_SUSPICIOUS_IMAGES` rule - * Enable mmap in sqlite3 - * Use new strings in the HTTP code - * Improve google perftools invocation - * Improve performance profiling in http test - * Reorganize includes to reduce namespace pollution - * Allow specific sections printing in configdump command - * Rework workers signals handlers to be chained if needed - * Update socketpair utility function - * Add control_path option for rspamd control protocol - * Fix ownership when listening on UNIX sockets - * Rework signals processing in main - * Remove extra tools from rspamd (they live in rspamadm now) - * Remove global rspamd_main - * Add global timeout for the overall task processing (8 seconds by default) - * Sanitize NULL values for fuzzy backend - * Store NM between encrypt/decrypt - * Add textpart:get_words_count method - * Fix generic DNS request in lua - * Tune hfilter weights - * Add support of IPv6 in hfilter - * Fix parsing of HTTP headers with IP addresses - * Sync with the recent libucl - * Various minor bugfixes + * Add rspamd control interface: + - support `stat` command to get runtime stats of rspamd workers + - support `reload` command to reload runtime elements (e.g. sqlite3 databases) + * Rework curve25519 library for modular design: + - add Sandy2x implementation by Tung Chou + - fix CPU detection for variables loading assembly + - add testing for curve25519 ECDH + * New fixed strings library + * Add `R_SUSPICIOUS_IMAGES` rule + * Enable mmap in sqlite3 + * Use new strings in the HTTP code + * Improve google perftools invocation + * Improve performance profiling in http test + * Reorganize includes to reduce namespace pollution + * Allow specific sections printing in configdump command + * Rework workers signals handlers to be chained if needed + * Update socketpair utility function + * Add control_path option for rspamd control protocol + * Fix ownership when listening on UNIX sockets + * Rework signals processing in main + * Remove extra tools from rspamd (they live in rspamadm now) + * Remove global rspamd_main + * Add global timeout for the overall task processing (8 seconds by default) + * Sanitize NULL values for fuzzy backend + * Store NM between encrypt/decrypt + * Add textpart:get_words_count method + * Fix generic DNS request in lua + * Tune hfilter weights + * Add support of IPv6 in hfilter + * Fix parsing of HTTP headers with IP addresses + * Sync with the recent libucl + * Various minor bugfixes 1.0.4: - * Add configdump routine to rspamadm - * Implement retransmits for fuzzy_check plugin - * Fix events processing for learning anf checking fuzzy hashes - * Avoid dependency on unneeded and uncompatible glib include - * Add `historyreset` command to the controller - * Fix loading of tokenizer config from dump (#389) - * Add sorting hints for the history - * Allow custom lua scripts for users/languages extraction (#388) - * Do not add FORGED_RECIPIENTS when 'To' is missing (#387) - * Do not add R_UNDISC_RCPT when 'To' is missing (#387) - * Add encryption to fuzzy check plugin - * Add encryption for fuzzy storage - * Add new epoch for encrypted fuzzy request - * Add encryption for `rspamd.com` storage - * Remove gmime processing for LDA mode as it is deadly broken - * Add routine to find end of headers position in mime messages - * Fix LDA headers folding - * Init libraries in rspamc client as well to avoid locale issues - * Avoid collision with locally installed includes - * Allocate and free memory with the same allocator in rspamadm (#385) - * Preserve expired fuzzy hashes counter - * Improvements in webui: - - Add favicon.ico - - Rework history table - - Fix sorting for the history - - Migrate to bootstrap 3 and jquery 2 - - Fix css bugs - - Add glyphicons - - Add reset history - - Improve history buttons - - Redraw graph to avoid display issues - - Webui is now MIT licensed to match licensing policy of rspamd + * Add configdump routine to rspamadm + * Implement retransmits for fuzzy_check plugin + * Fix events processing for learning anf checking fuzzy hashes + * Avoid dependency on unneeded and uncompatible glib include + * Add `historyreset` command to the controller + * Fix loading of tokenizer config from dump (#389) + * Add sorting hints for the history + * Allow custom lua scripts for users/languages extraction (#388) + * Do not add FORGED_RECIPIENTS when 'To' is missing (#387) + * Do not add R_UNDISC_RCPT when 'To' is missing (#387) + * Add encryption to fuzzy check plugin + * Add encryption for fuzzy storage + * Add new epoch for encrypted fuzzy request + * Add encryption for `rspamd.com` storage + * Remove gmime processing for LDA mode as it is deadly broken + * Add routine to find end of headers position in mime messages + * Fix LDA headers folding + * Init libraries in rspamc client as well to avoid locale issues + * Avoid collision with locally installed includes + * Allocate and free memory with the same allocator in rspamadm (#385) + * Preserve expired fuzzy hashes counter + * Improvements in webui: + - Add favicon.ico + - Rework history table + - Fix sorting for the history + - Migrate to bootstrap 3 and jquery 2 + - Fix css bugs + - Add glyphicons + - Add reset history + - Improve history buttons + - Redraw graph to avoid display issues + - Webui is now MIT licensed to match licensing policy of rspamd 1.0.3: - * Fix piechart clean slice (#380) - * Fix controller crashes when GString is reallocated (#381) - * Correctly set locale before start - * Set C locale for numeric values - * Add rspamadm routine: - - add `pw` command to manage passwords - - add `help` command for displaying help - - add `configtest` command to check configuration files - - add `keypair` command for generating encryption keys - - add `fuzzy_merge` routine to merge fuzzy sqlite databases - - add a simple manual page for rspamadm - * Allow metric registration for composite expressions - * Add strict mode for configtest - * Add logger counters - * Save and show learned messages count (#383) - * Add `no_stat` flag - * Add `task:set_flag` and `task:get_flags` (#382) - * Enable foreign keys in sqlite3 - * Remove orphaned shingles from fuzzy storage - * Optimize synchronization steps for fuzzy storage - * Allow delayed conditions registration - * Add lua API for conditions registering + * Fix piechart clean slice (#380) + * Fix controller crashes when GString is reallocated (#381) + * Correctly set locale before start + * Set C locale for numeric values + * Add rspamadm routine: + - add `pw` command to manage passwords + - add `help` command for displaying help + - add `configtest` command to check configuration files + - add `keypair` command for generating encryption keys + - add `fuzzy_merge` routine to merge fuzzy sqlite databases + - add a simple manual page for rspamadm + * Allow metric registration for composite expressions + * Add strict mode for configtest + * Add logger counters + * Save and show learned messages count (#383) + * Add `no_stat` flag + * Add `task:set_flag` and `task:get_flags` (#382) + * Enable foreign keys in sqlite3 + * Remove orphaned shingles from fuzzy storage + * Optimize synchronization steps for fuzzy storage + * Allow delayed conditions registration + * Add lua API for conditions registering 1.0.2: - * Fix critical bug in webui that prevents password from being sent - * Rework webui view: - - Switch to d3.js for graphs - - Improve piechart look - - Rework colors for piechart - - Fix layout for symbols - - Fix refresh button - * Add descriptions for whitelist maps - * Fix build on arm (#379) - * Fix issue with the last element in the radix trie - * Add more tests for radix trie algorithm - * Allow to extract URLs from query strings of other URLs (#361) - * Initialize rrd fields before writing to file - * Fix double free if no password has been specified + * Fix critical bug in webui that prevents password from being sent + * Rework webui view: + - Switch to d3.js for graphs + - Improve piechart look + - Rework colors for piechart + - Fix layout for symbols + - Fix refresh button + * Add descriptions for whitelist maps + * Fix build on arm (#379) + * Fix issue with the last element in the radix trie + * Add more tests for radix trie algorithm + * Allow to extract URLs from query strings of other URLs (#361) + * Initialize rrd fields before writing to file + * Fix double free if no password has been specified 1.0.1: - * Add writing to rrd from the controller - * Fixed lots of bugs in rrd code - * Adopt new DNS API in hfilter plugin (by @AlexeySa) - * Allow only one controller process to manage rrd file - * Set event base for fuzzy calls - * Improve fuzzy IO errors logging - * Add rra extraction function to rrd library - * Add graph handler to the controller - * Cache correct passwords to avoid too high CPU usage when working with webui - * Controller sockets are owned by router do not export them to task - * Optimize logging by skipping hash table search if it's empty - * Fix loading issue with broken statfiles - * Print assertions from glib to rspamd logger - * Load legacy `lua/rspamd.local.lua` - * Update webui with some fixes to learning and scanning + * Add writing to rrd from the controller + * Fixed lots of bugs in rrd code + * Adopt new DNS API in hfilter plugin (by @AlexeySa) + * Allow only one controller process to manage rrd file + * Set event base for fuzzy calls + * Improve fuzzy IO errors logging + * Add rra extraction function to rrd library + * Add graph handler to the controller + * Cache correct passwords to avoid too high CPU usage when working with webui + * Controller sockets are owned by router do not export them to task + * Optimize logging by skipping hash table search if it's empty + * Fix loading issue with broken statfiles + * Print assertions from glib to rspamd logger + * Load legacy `lua/rspamd.local.lua` + * Update webui with some fixes to learning and scanning 1.0.0: - * Rework symbols processing: - - Improve sorting logic for symbols - - Organize processing into multiple stages - - Added asynchronous watchers for symbols - - Added ability to organize dependencies between symbols - * Fixed URL redirector: - - Use optimized POE loop - - Organize dependencies - - Fix startup - * New sqlite3 backend: - - Allow to have per-languages and per-user statistics - - Allow sqlite3 to be used as statistics backend - * Store tokenizer configuration within statfiles - * Improve bayes statistics: - - Use headers and images metainformation in bayes - - Suggest using of pre-processed tokens for statistics - - Fix tokens normalization for OSB algorithm - * Rewrite url parsing: - - Fix numerous issues with url extraction and normalization - - Fix mailto urls - * Fix settings plugin to allow custom actions scores - * Improve rbl plugin - * Allow capturing patterns in rspamd lua regexp library - * Add GTUBE support - * Fix spamc legacy support - * Add DKIM support to RBL module - * Fix issues with multiple DKIM signatures - * Fix issue if rspamd cannot create statfiles (#331) - * Rework parts and task structure: - - Now text_parts, parts and received are arrays - - Pre-allocate arrays with some reasonable defaults - - Use arrays instead of lists in plugins and checks - - Remove unused fields from task structure - - Rework mime_foreach callback function - - Remove deprecated scan_milliseconds field - * Add ip_score plugin support (not enabled by default): - - Can check for asn/country and network using DNS lookups - - Can store and load reputation from redis server - * Improve PARTS_DIFFER rule to count merely different words - * New HTML parser: - - Parses HTML parts using a set of state machines - - Extracts useful data and exports it to lua functions: - + Styles - + Images - + URLs - + Colors - + Structure elements - - Added HTML rules for some checks - * New version of LUA DNS API - * Table versions of many functions in LUA API - * Improve rspamc client: - - Print execution time - - Allow executing of external commands and passing output to them - - Allow mime output mode when rspamc alters message according to rspamd - checks and send it to an external command or stdout - * Allow scanning of local files using HTTP requests - * Rework configuration system: - - Rules are now moved from the $CONFDIR to $RULESDIR to avoid ambiguity - - All modules configurations are now split in $CONFDIR/modules.d/* to - simplify upgrades - - Move hfilter to plugins - - Allow plugins and rules to define default scores to simplify metrics - setup - - Include overrides for all modules to honor local/automatic parameters - - Tune scores for many modules - * Rework and enable DMARC plugin - * Add whitelist plugin for SPF/DKIM/DMARC based whitelisting - * Add some common domains to whitelists shipped with rspamd - * Rework logging: - - Now each log entry supports module name and a `tag`. Tag is used to - identify unique objects (such as tasks) when checking log files - - It is possible to turn on debugging for the specific modules - - Systemd logging is fixed - * Improve spamassassin plugin. - - Now headers are matched more like SA - - Improve support of Message-ID - - Add support of ToCc header type - - Fix :addr and :name in headers regexps - * Resurrect rrd support code - * Save controller stats between restarts - * Fixed tonns of bugs - * Added tonns of minor improvements and features - * Added more unit tests - * Create functional tests framework - * Added documentation for missing modules - * Added rpm/deb repositories and scripts - * Updated WebUI and libucl externals + * Rework symbols processing: + - Improve sorting logic for symbols + - Organize processing into multiple stages + - Added asynchronous watchers for symbols + - Added ability to organize dependencies between symbols + * Fixed URL redirector: + - Use optimized POE loop + - Organize dependencies + - Fix startup + * New sqlite3 backend: + - Allow to have per-languages and per-user statistics + - Allow sqlite3 to be used as statistics backend + * Store tokenizer configuration within statfiles + * Improve bayes statistics: + - Use headers and images metainformation in bayes + - Suggest using of pre-processed tokens for statistics + - Fix tokens normalization for OSB algorithm + * Rewrite url parsing: + - Fix numerous issues with url extraction and normalization + - Fix mailto urls + * Fix settings plugin to allow custom actions scores + * Improve rbl plugin + * Allow capturing patterns in rspamd lua regexp library + * Add GTUBE support + * Fix spamc legacy support + * Add DKIM support to RBL module + * Fix issues with multiple DKIM signatures + * Fix issue if rspamd cannot create statfiles (#331) + * Rework parts and task structure: + - Now text_parts, parts and received are arrays + - Pre-allocate arrays with some reasonable defaults + - Use arrays instead of lists in plugins and checks + - Remove unused fields from task structure + - Rework mime_foreach callback function + - Remove deprecated scan_milliseconds field + * Add ip_score plugin support (not enabled by default): + - Can check for asn/country and network using DNS lookups + - Can store and load reputation from redis server + * Improve PARTS_DIFFER rule to count merely different words + * New HTML parser: + - Parses HTML parts using a set of state machines + - Extracts useful data and exports it to lua functions: + + Styles + + Images + + URLs + + Colors + + Structure elements + - Added HTML rules for some checks + * New version of LUA DNS API + * Table versions of many functions in LUA API + * Improve rspamc client: + - Print execution time + - Allow executing of external commands and passing output to them + - Allow mime output mode when rspamc alters message according to rspamd + checks and send it to an external command or stdout + * Allow scanning of local files using HTTP requests + * Rework configuration system: + - Rules are now moved from the $CONFDIR to $RULESDIR to avoid ambiguity + - All modules configurations are now split in $CONFDIR/modules.d/* to + simplify upgrades + - Move hfilter to plugins + - Allow plugins and rules to define default scores to simplify metrics + setup + - Include overrides for all modules to honor local/automatic parameters + - Tune scores for many modules + * Rework and enable DMARC plugin + * Add whitelist plugin for SPF/DKIM/DMARC based whitelisting + * Add some common domains to whitelists shipped with rspamd + * Rework logging: + - Now each log entry supports module name and a `tag`. Tag is used to + identify unique objects (such as tasks) when checking log files + - It is possible to turn on debugging for the specific modules + - Systemd logging is fixed + * Improve spamassassin plugin. + - Now headers are matched more like SA + - Improve support of Message-ID + - Add support of ToCc header type + - Fix :addr and :name in headers regexps + * Resurrect rrd support code + * Save controller stats between restarts + * Fixed tonns of bugs + * Added tonns of minor improvements and features + * Added more unit tests + * Create functional tests framework + * Added documentation for missing modules + * Added rpm/deb repositories and scripts + * Updated WebUI and libucl externals 0.9.10: - * Do not dereference null pointer on learning. - * Fix some extreme cases in BAYES. - * Add a workaround to avoid bad HTML messages breaking. - * Build with -O2 flags by default. - * Add constraints to limit DNS requests count per task. - * Add workaround for SURBL DNS flood. - * Set error if rspamd cannot learn anything. + * Do not dereference null pointer on learning. + * Fix some extreme cases in BAYES. + * Add a workaround to avoid bad HTML messages breaking. + * Build with -O2 flags by default. + * Add constraints to limit DNS requests count per task. + * Add workaround for SURBL DNS flood. + * Set error if rspamd cannot learn anything. 0.9.9: - * Don't use RWL_SPAMHAUS_WL (unknown result) for whitelisting (by @fatalbanana) - * Import updated public suffix list (by @fatalbanana) - * Remove debug message - * Fix settings (by @fatalbanana) - * Remove duplicated symbol registration - * Use WAL for fuzzy storage - * RBL fixes (by @fatalbanana): - - silence errors; - - yield unknown results from RBLs; - - fix scoring for DNSWL; - - fix use of RBL name as symbol; - - ignore RBL names that would not be yielded; - * Support captures in regular expressions - * Add captures support to lua_regexp - * Support dist on FreeBSD and Darwin - * Add RCVD_IN_DNSWL_NONE as whitelisting exclusion (by @fatalbanana) - * Multiple fixes to URL detection: - - support port definition; - - fix query and path recognition; - - fix parsing of multiple slashes in URL; - - fix parsing query just after port; - - fix path field in `url:to_table` method; - - improve support of IP based URLs. - * Set ignore_whitelists = true for RECEIVED_SPAMHAUS_XBL (by @fatalbanana) - * Add GTUBE support - * Ignore User header in SA mode + * Don't use RWL_SPAMHAUS_WL (unknown result) for whitelisting (by @fatalbanana) + * Import updated public suffix list (by @fatalbanana) + * Remove debug message + * Fix settings (by @fatalbanana) + * Remove duplicated symbol registration + * Use WAL for fuzzy storage + * RBL fixes (by @fatalbanana): + - silence errors; + - yield unknown results from RBLs; + - fix scoring for DNSWL; + - fix use of RBL name as symbol; + - ignore RBL names that would not be yielded; + * Support captures in regular expressions + * Add captures support to lua_regexp + * Support dist on FreeBSD and Darwin + * Add RCVD_IN_DNSWL_NONE as whitelisting exclusion (by @fatalbanana) + * Multiple fixes to URL detection: + - support port definition; + - fix query and path recognition; + - fix parsing of multiple slashes in URL; + - fix parsing query just after port; + - fix path field in `url:to_table` method; + - improve support of IP based URLs. + * Set ignore_whitelists = true for RECEIVED_SPAMHAUS_XBL (by @fatalbanana) + * Add GTUBE support + * Ignore User header in SA mode 0.9.8: - * Fix critical bug in bayes classifier (#305) - * Fix critical bug in RBL module (by @fatalbanana) - * Fix and rework settings plugin. - * Fix get_all_opts for a case of non-iterable options. - * Use tld for redirector's matching. + * Fix critical bug in bayes classifier (#305) + * Fix critical bug in RBL module (by @fatalbanana) + * Fix and rework settings plugin. + * Fix get_all_opts for a case of non-iterable options. + * Use tld for redirector's matching. 0.9.7: - * Add whitelist_exception setting to RBL module (by @fatalbanana) - * Don't use RWL_MAILSPIKE_POSSIBLE or DNSWL_BLOCKED for whitelisting (by - @fatalbanana) - * Fix extreme cases in bayes classifier. - * Fix parsing of urls with '?' at the end of hostname. - * Update interface. - * Fix number of issues with webui interaction. - * Fix saving maps. - * Allow user@ and @domain matches in multimap. - * Fix issues with bounces From processing. - * Fix abs/fabs misuse. - * Fix builds on suse and arch linux distributions. + * Add whitelist_exception setting to RBL module (by @fatalbanana) + * Don't use RWL_MAILSPIKE_POSSIBLE or DNSWL_BLOCKED for whitelisting (by + @fatalbanana) + * Fix extreme cases in bayes classifier. + * Fix parsing of urls with '?' at the end of hostname. + * Update interface. + * Fix number of issues with webui interaction. + * Fix saving maps. + * Allow user@ and @domain matches in multimap. + * Fix issues with bounces From processing. + * Fix abs/fabs misuse. + * Fix builds on suse and arch linux distributions. 0.9.6: - * Fix memory leak if mime cannot be parsed. - * Fix dkim cache expiration. - * Fix issues with redirector HTTP response. - * Fix abnormal connection closing with certains messages with a high score - (issue #296) - * Fix redirector installation. - * Use specific POE loop for some systems. - * Fix number of issues in URL redirector. - * Fix selecting URLs for sending to redirector. + * Fix memory leak if mime cannot be parsed. + * Fix dkim cache expiration. + * Fix issues with redirector HTTP response. + * Fix abnormal connection closing with certains messages with a high score + (issue #296) + * Fix redirector installation. + * Use specific POE loop for some systems. + * Fix number of issues in URL redirector. + * Fix selecting URLs for sending to redirector. 0.9.5: - * Avoid double free when extending HTTP message. - * Fix double free if multiple classifiers are defined. - * Fix misprint in spamassassin plugin. - * Fix cpuid invocation on i386. - * Fix ownership issues for zero-copy decode. - * Allow __len metamethod on rspamd{text}. - * Add base64 decoding lua utility. - * Fix build on FreeBSD - * Skip spaces at the beginning of mime messages. - * DBL_ABUSE_REDIR should not have significant weight. - * Allow to split by lua_regexp rspamd{text} objects. - * Allow to specify custom stop pattern for lua_tcp. + * Avoid double free when extending HTTP message. + * Fix double free if multiple classifiers are defined. + * Fix misprint in spamassassin plugin. + * Fix cpuid invocation on i386. + * Fix ownership issues for zero-copy decode. + * Allow __len metamethod on rspamd{text}. + * Add base64 decoding lua utility. + * Fix build on FreeBSD + * Skip spaces at the beginning of mime messages. + * DBL_ABUSE_REDIR should not have significant weight. + * Allow to split by lua_regexp rspamd{text} objects. + * Allow to specify custom stop pattern for lua_tcp. 0.9.4: - * Fix critical bugs in tokenization algorithm - * Write unit tests for tokenization - * Add documentation for lua_tcp - * Switch off legacy tokenization by default. - * Fix critical bugs in words normalization - * Add lua bindings to tokenizer. - * Implement storing of HTTP headers inside task - * Add lua API to accerss HTTP headers data - * Implemented base64 encoding suitable for MIME - * Use caseless hash and equal functions for HTTP request headers. - * Improve debian architectures support (by @dottedmag) + * Fix critical bugs in tokenization algorithm + * Write unit tests for tokenization + * Add documentation for lua_tcp + * Switch off legacy tokenization by default. + * Fix critical bugs in words normalization + * Add lua bindings to tokenizer. + * Implement storing of HTTP headers inside task + * Add lua API to accerss HTTP headers data + * Implemented base64 encoding suitable for MIME + * Use caseless hash and equal functions for HTTP request headers. + * Improve debian architectures support (by @dottedmag) 0.9.3: - * Revert incorrect regexp change that broke the default rules - * Fix lua_tcp module + * Revert incorrect regexp change that broke the default rules + * Fix lua_tcp module 0.9.2: - * Fix error on spawning unique workers. - * Add preliminary version of generic LUA TCP requests API. - * Use lua 5.1 if luajit is not available (Arm64, PowerPC, s390x etc) - * Fix fuzzy mime strings with only type. - * Improve thunderbird sanity checks. - * Fix critical bug on matching regular expressions. - * Make hiredis optional dependency. - * Fix multiple bugs in daemon reloading + * Fix error on spawning unique workers. + * Add preliminary version of generic LUA TCP requests API. + * Use lua 5.1 if luajit is not available (Arm64, PowerPC, s390x etc) + * Fix fuzzy mime strings with only type. + * Improve thunderbird sanity checks. + * Fix critical bug on matching regular expressions. + * Make hiredis optional dependency. + * Fix multiple bugs in daemon reloading 0.9.1: - * Restore utf8 validation for regular expressions to avoid crashes - * Fix symbols displaying in the interface - * Add symbol groups to the interface - * Fix maps ID parsing in the controller - * Add multimap and regexp modules documentation - * Backport fixes from libucl - * Fix debian package (by @dottedmag) - * Rework XXH32 invocations + * Restore utf8 validation for regular expressions to avoid crashes + * Fix symbols displaying in the interface + * Add symbol groups to the interface + * Fix maps ID parsing in the controller + * Add multimap and regexp modules documentation + * Backport fixes from libucl + * Fix debian package (by @dottedmag) + * Rework XXH32 invocations 0.9.0: - * Add support of the fast and secure protocol level encryption: - - curve25519 is used for key exchange; - - chacha20/poly1305 cryptobox construction for bulk encryption; - - zero latency overhead; - - encrypting and balancing HTTP proxy worker - * Rework expressions and create new expressions library: - - aggressive optimizations based on the abstract syntax tree; - - abstract expressions support (regular expressions, functions, lua modules - composites and so on) - - New comparison and '+' operators support - - New greedy algorithm to minimize execution time of expressions and - all symbols - - Dynamic expressions benchmark and reoptimizations - * Many improvements to the LUA API: - - reworked logger module allowing to do pretty print of the most of lua - types (including tables and userdata classes) - - reworked lua redis and lua HTTP to support more features - - added opaque type for passing large text chunks without copying - - new regexp module with many auxiliary functions (e.g. `re:split`) - * LuaJIT is now the default requirement for rspamd allowing to speed up lua - execution by a large margin (however, plain lua is still supported) - * New plugins: - - spamassassin rules plugin that allows to load and re-use the most of - SA rules natively - - DMARC plugin that evaluates SPF and DKIM policies to the domain policies - - many old plugins has been reworked to implement new features and improve - stability - * New aho-corasic trie implementation from @mischasan that allows to load and - use hundreds of thousands of patterns with no influence on load - * Support of PCRE JIT and PCRE JIT fast path modes that significantly improves - the performance of regular expressions if supported by PCRE - * New URLs parser and extractor: - - removed legacy code that was useless for url finding - - reworked algorithms of URL parsing for more precise and accurate results - - added top-level-domains tree from http://publicsuffix.org - - improved emails parsing - - removed many phishing false positives due to TLD tree check - * New statistics infrastructure: - - created a separate layer of statistic library - - improved OSB-Bayes by re-weighting tokens according to the original - academic paper and `crm114` implementation, which reduced false positives - rate significantly - - created learn cache to avoid double learning of statistics and providing - an efficient way to re-learn class for a message - - created abstract layers for different statistics backends - - implemented new tokenization algorithms with fast or secure (siphash) - hashes to generate statistics features - * Reworked utf8 tokenization that previously corrupted all UTF8 words (minor - incompatibility with old fuzzy hashes with utf-8 symbols) - * SPF module has been completely rewritten to support complex cases of - `include` and `redirect` within SPF records - * DKIM module now supports multiple signatures - * Controller passwords can now be stored encrypted by `PBKDF2-HMAC` in the - configuration file - * Many hand-written HTTP clients has been replaced with the common rspamd - http module - * New test framework: - - import lua `telescope` test framework - - add unit tests for many rspamd modules and routines - - create a unit test for each possible bug found - - use luajit ffi for testing C code - - added preliminary support of functional testing by creating tasks from lua - * Randomize hash seed to avoid certain hash tables vulnerabilities - * Documentation improvements: - - added documentation for the vast majority of rspamd modules - - added documentation for rspamd protocol - - added documentation for the most of rspamd LUA extensions - * Fixed tonns of bugs and memory leaks - * Added tonns of minor features + * Add support of the fast and secure protocol level encryption: + - curve25519 is used for key exchange; + - chacha20/poly1305 cryptobox construction for bulk encryption; + - zero latency overhead; + - encrypting and balancing HTTP proxy worker + * Rework expressions and create new expressions library: + - aggressive optimizations based on the abstract syntax tree; + - abstract expressions support (regular expressions, functions, lua modules + composites and so on) + - New comparison and '+' operators support + - New greedy algorithm to minimize execution time of expressions and + all symbols + - Dynamic expressions benchmark and reoptimizations + * Many improvements to the LUA API: + - reworked logger module allowing to do pretty print of the most of lua + types (including tables and userdata classes) + - reworked lua redis and lua HTTP to support more features + - added opaque type for passing large text chunks without copying + - new regexp module with many auxiliary functions (e.g. `re:split`) + * LuaJIT is now the default requirement for rspamd allowing to speed up lua + execution by a large margin (however, plain lua is still supported) + * New plugins: + - spamassassin rules plugin that allows to load and re-use the most of + SA rules natively + - DMARC plugin that evaluates SPF and DKIM policies to the domain policies + - many old plugins has been reworked to implement new features and improve + stability + * New aho-corasic trie implementation from @mischasan that allows to load and + use hundreds of thousands of patterns with no influence on load + * Support of PCRE JIT and PCRE JIT fast path modes that significantly improves + the performance of regular expressions if supported by PCRE + * New URLs parser and extractor: + - removed legacy code that was useless for url finding + - reworked algorithms of URL parsing for more precise and accurate results + - added top-level-domains tree from http://publicsuffix.org + - improved emails parsing + - removed many phishing false positives due to TLD tree check + * New statistics infrastructure: + - created a separate layer of statistic library + - improved OSB-Bayes by re-weighting tokens according to the original + academic paper and `crm114` implementation, which reduced false positives + rate significantly + - created learn cache to avoid double learning of statistics and providing + an efficient way to re-learn class for a message + - created abstract layers for different statistics backends + - implemented new tokenization algorithms with fast or secure (siphash) + hashes to generate statistics features + * Reworked utf8 tokenization that previously corrupted all UTF8 words (minor + incompatibility with old fuzzy hashes with utf-8 symbols) + * SPF module has been completely rewritten to support complex cases of + `include` and `redirect` within SPF records + * DKIM module now supports multiple signatures + * Controller passwords can now be stored encrypted by `PBKDF2-HMAC` in the + configuration file + * Many hand-written HTTP clients has been replaced with the common rspamd + http module + * New test framework: + - import lua `telescope` test framework + - add unit tests for many rspamd modules and routines + - create a unit test for each possible bug found + - use luajit ffi for testing C code + - added preliminary support of functional testing by creating tasks from lua + * Randomize hash seed to avoid certain hash tables vulnerabilities + * Documentation improvements: + - added documentation for the vast majority of rspamd modules + - added documentation for rspamd protocol + - added documentation for the most of rspamd LUA extensions + * Fixed tonns of bugs and memory leaks + * Added tonns of minor features 0.8.3: - * Various critical fixes in distribution (by @dottedmag and @fatalbanana) - * Fixed bugs in url detector to parse certain patterns - * Add default host and helo for a client - * Some sanity checks for tokenizer and classifier - * Reiterate on systemd support - * Fix missing symbol registration - * Add support of spamc compatible output - * Filter double-dots in rbl.lua validate_dns (by @fatalbanana) - * Update ucl submodule due to critical bugfix + * Various critical fixes in distribution (by @dottedmag and @fatalbanana) + * Fixed bugs in url detector to parse certain patterns + * Add default host and helo for a client + * Some sanity checks for tokenizer and classifier + * Reiterate on systemd support + * Fix missing symbol registration + * Add support of spamc compatible output + * Filter double-dots in rbl.lua validate_dns (by @fatalbanana) + * Update ucl submodule due to critical bugfix 0.8.2: - * Create fuzzy db if it does not exist - * Fix: Centos init script: configtest() (by @AlexeySa) - * Enable one_shot for RECEIVED_SPAMHAUS_XBL - Fixes #102 (by @fatalbanana) - * Update Exim patch (by @fatalbanana) - * Fix processing of unix sockets. - * Allow applying settings to authenticated users (by @fatalbanana) - * Make settings priorities work as documented (by @fatalbanana) - * Fix race condition in symbols planner - * Add DNSWL_BLOCKED symbol (by @fatalbanana) - * Make Exim pass usernames to rspamd (by @fatalbanana) - * Update RBL module (by @fatalbanana): - - fix indentation; - - collapse loops; - - avoid calling for un-needed information; - - allow disabling RBLs for authenticated users - * once_received.lua: Fix indentation & add exclusion for authenticated users (by @fatalbanana) - * hfilter.lua: Add exclusion for authenticated users (by @AlexeySa) - * Updates to hfilter rules (by @AlexeySa) - * Set empty <> user or addr for msgs without FROM (by @eneq123) - * Fix: attempt to index field '?' (a nil value) (by @eneq123) - * Fix: if not exist Date-header (by @AlexeySa) - * Add task:get_content() method. - * rbl.lua: Ignore private IP space (by @fatalbanana) - * Allow to check radix maps from lua by rspamd{ip} - * Make local exclusions configurable per-RBL (by @fatalbanana) - * Add rspamd_config:radix_from_config() (by @fatalbanana) - * Support emails dnsbl in rbl (by @fatalbanana) - * Complete rework of url extraction logic - * Allow customizations for unix sockets. (fixes #182) - * Set lua path according to rspamd settings. - * Import lua-functional for plugins stuff. - * Completely rewrite multimap plugin in functional style. - * Fix FORGED_MUA_THUNDERBIRD_MSGID (fixes #186) - * Check IPv6 addresses at dnswl.org and Spamhaus whitelist (by @fatalbanana) - * Add lowercase utility for utf8 strings. - * Various fixes to build system - * Updated debian configuration infrastructure (by @dottedmag) + * Create fuzzy db if it does not exist + * Fix: Centos init script: configtest() (by @AlexeySa) + * Enable one_shot for RECEIVED_SPAMHAUS_XBL - Fixes #102 (by @fatalbanana) + * Update Exim patch (by @fatalbanana) + * Fix processing of unix sockets. + * Allow applying settings to authenticated users (by @fatalbanana) + * Make settings priorities work as documented (by @fatalbanana) + * Fix race condition in symbols planner + * Add DNSWL_BLOCKED symbol (by @fatalbanana) + * Make Exim pass usernames to rspamd (by @fatalbanana) + * Update RBL module (by @fatalbanana): + - fix indentation; + - collapse loops; + - avoid calling for un-needed information; + - allow disabling RBLs for authenticated users + * once_received.lua: Fix indentation & add exclusion for authenticated users (by @fatalbanana) + * hfilter.lua: Add exclusion for authenticated users (by @AlexeySa) + * Updates to hfilter rules (by @AlexeySa) + * Set empty <> user or addr for msgs without FROM (by @eneq123) + * Fix: attempt to index field '?' (a nil value) (by @eneq123) + * Fix: if not exist Date-header (by @AlexeySa) + * Add task:get_content() method. + * rbl.lua: Ignore private IP space (by @fatalbanana) + * Allow to check radix maps from lua by rspamd{ip} + * Make local exclusions configurable per-RBL (by @fatalbanana) + * Add rspamd_config:radix_from_config() (by @fatalbanana) + * Support emails dnsbl in rbl (by @fatalbanana) + * Complete rework of url extraction logic + * Allow customizations for unix sockets. (fixes #182) + * Set lua path according to rspamd settings. + * Import lua-functional for plugins stuff. + * Completely rewrite multimap plugin in functional style. + * Fix FORGED_MUA_THUNDERBIRD_MSGID (fixes #186) + * Check IPv6 addresses at dnswl.org and Spamhaus whitelist (by @fatalbanana) + * Add lowercase utility for utf8 strings. + * Various fixes to build system + * Updated debian configuration infrastructure (by @dottedmag) 0.8.1: - * Add sqlite and perl as dependencies for RPM/Debian packages (by @fatalbanana) - * Remove whitelist.lua from RPM file list (by @fatalbanana) - * Make Exim pass hostnames to rspamd (by @fatalbanana) - * Fix building on Fedora (by @fatalbanana) - * Add toggle for disabling installation of systemd units on Linux (by @fatalbanana) - * Fix double format rounding that caused output corruption (reported by @fatalbanana) - * Revert broken change for destructors ordering that led to memory corruption - * Do not reset symbols case of settings if parsed from lua (reported by @andrejzverev) - * Fix build on SunOS (by @wiedi) - * Fix multiple crashes on broken DKIM DNS records - * Fix critical issue with composites weights removing - * Fix memory corruption in composites processing code - * Ignore non-SPF TXT records when parsing SPF includes + * Add sqlite and perl as dependencies for RPM/Debian packages (by @fatalbanana) + * Remove whitelist.lua from RPM file list (by @fatalbanana) + * Make Exim pass hostnames to rspamd (by @fatalbanana) + * Fix building on Fedora (by @fatalbanana) + * Add toggle for disabling installation of systemd units on Linux (by @fatalbanana) + * Fix double format rounding that caused output corruption (reported by @fatalbanana) + * Revert broken change for destructors ordering that led to memory corruption + * Do not reset symbols case of settings if parsed from lua (reported by @andrejzverev) + * Fix build on SunOS (by @wiedi) + * Fix multiple crashes on broken DKIM DNS records + * Fix critical issue with composites weights removing + * Fix memory corruption in composites processing code + * Ignore non-SPF TXT records when parsing SPF includes 0.8.0: - * New fuzzy check logic: - - use shingles algorithm for fuzzy matching - - use blake2 instead of md5 for larger output space - - combine fuzzy and strict matching - - allow to organize private storages by means of keys - - preserve compatibility with previous versions - * New fuzzy storage: - - use sqlite instead of own memory based hash tables - - rework commands interface - - add conversion from the old format - - add fuzzy match by shignles - - support old rspamd versions - * Add lemmatizing for words used in fuzzy hashes that allows to improve match - quality by using of the first forms of all words - * Rework language detection - * Fix several critical bugs, memory leaks and deadlocks: - - memory leak in HTML nodes parsing - - deadlock in logger code - - deadlock in signals processing - - crashes in fuzzy_storage - - crashes in tokenizers if the input was empty - * Import new libucl with several bugfixes and improvements - * Support listening on ipv6 addresses only - * Fix macro expansion in SPF module - * Several bugfixes in DKIM module - * Add load headers support for mime parts to the lua API - * Add documentation for: - - workers in general - - fuzzy_storage worker - - fuzzy_check plugin - - mimepart and textpart lua API modules + * New fuzzy check logic: + - use shingles algorithm for fuzzy matching + - use blake2 instead of md5 for larger output space + - combine fuzzy and strict matching + - allow to organize private storages by means of keys + - preserve compatibility with previous versions + * New fuzzy storage: + - use sqlite instead of own memory based hash tables + - rework commands interface + - add conversion from the old format + - add fuzzy match by shignles + - support old rspamd versions + * Add lemmatizing for words used in fuzzy hashes that allows to improve match + quality by using of the first forms of all words + * Rework language detection + * Fix several critical bugs, memory leaks and deadlocks: + - memory leak in HTML nodes parsing + - deadlock in logger code + - deadlock in signals processing + - crashes in fuzzy_storage + - crashes in tokenizers if the input was empty + * Import new libucl with several bugfixes and improvements + * Support listening on ipv6 addresses only + * Fix macro expansion in SPF module + * Several bugfixes in DKIM module + * Add load headers support for mime parts to the lua API + * Add documentation for: + - workers in general + - fuzzy_storage worker + - fuzzy_check plugin + - mimepart and textpart lua API modules 0.7.6: - * Apply boundary fix for dkim simple canonization - * Fix ping command - * Return nil if header was not found in lua_task - * Fix hang in upstreams revive logic - * Decode entitles when normalizing HTML parts - * Fix logic of finding URLs in HTML parts - * Do not include \0 into length of text when performing conversion to utf8 - * Fix raw vs parsed reperesentations - Raw parts are now: - - decoded b64/qp, but *NOT* converted to utf-8 - Processed parts are now: - - converted to UTF-8 - - normalized if needed (e.g. HTML tags are stripped) - * Rework DKIM canonization to line based - * Fix fuzzy hashes adding - * Use more specific hash function for fuzzy - * Fix leaking of iconv descriptors - * Fix PTR resolving in lua resolver - * Rework spf module. - - Copy data to memory pool as cached record might be destroyed causing - freed memory being passed to the protocol output (use after free) - - Allow SPF_NEUTRAL policy to be handled separately - - Add R_SPF_NEUTRAL to the default config - * Rework `register_symbols` function - * Allow to disable components of hfilter + * Apply boundary fix for dkim simple canonization + * Fix ping command + * Return nil if header was not found in lua_task + * Fix hang in upstreams revive logic + * Decode entitles when normalizing HTML parts + * Fix logic of finding URLs in HTML parts + * Do not include \0 into length of text when performing conversion to utf8 + * Fix raw vs parsed reperesentations + Raw parts are now: + - decoded b64/qp, but *NOT* converted to utf-8 + Processed parts are now: + - converted to UTF-8 + - normalized if needed (e.g. HTML tags are stripped) + * Rework DKIM canonization to line based + * Fix fuzzy hashes adding + * Use more specific hash function for fuzzy + * Fix leaking of iconv descriptors + * Fix PTR resolving in lua resolver + * Rework spf module. + - Copy data to memory pool as cached record might be destroyed causing + freed memory being passed to the protocol output (use after free) + - Allow SPF_NEUTRAL policy to be handled separately + - Add R_SPF_NEUTRAL to the default config + * Rework `register_symbols` function + * Allow to disable components of hfilter 0.7.5: - * Fix owner when creating folder /run/rspamd (by @sfirmery) - * Fix IP validity checks - * Decode URLs obtained from HTML tags - * Fix crash with unweighted upstreams - * Stop processing headers in parts - * Set sockaddr.sa_family properly when connectig to upstreams - * Fix reload issues in surbl and fuzzy_check (reported by @citrin) - * Fix timeouts in redirector - * Improve lua errors reporting - * Fix lua closures processing in libucl - * Rework calling of lua functions from regexp module - * Choose raw regexp for raw headers - * Rework conversion to utf since glib one is broken - * Ignore SGML style tags in html - * Fix old bug with non-capturing https urls - * Fix memory corruption on fuzzy reload (reported by @citrin) - * Fix percents display in rspamc - * Fix buffer update for DKIM - * Do not validate utf for raw headers + * Fix owner when creating folder /run/rspamd (by @sfirmery) + * Fix IP validity checks + * Decode URLs obtained from HTML tags + * Fix crash with unweighted upstreams + * Stop processing headers in parts + * Set sockaddr.sa_family properly when connectig to upstreams + * Fix reload issues in surbl and fuzzy_check (reported by @citrin) + * Fix timeouts in redirector + * Improve lua errors reporting + * Fix lua closures processing in libucl + * Rework calling of lua functions from regexp module + * Choose raw regexp for raw headers + * Rework conversion to utf since glib one is broken + * Ignore SGML style tags in html + * Fix old bug with non-capturing https urls + * Fix memory corruption on fuzzy reload (reported by @citrin) + * Fix percents display in rspamc + * Fix buffer update for DKIM + * Do not validate utf for raw headers 0.7.4: - * Fix build under *BSD - * Detect HAN unicode script - * Implement language detection heuristic for text parts - * Fix time output in history - * Improve piechart coloring - * Fix \r\n conversion in DKIM module (reported by @citrin) - * Try to detect systems with no IPv6 support - * Fix multiple/single values in use settings (reported by @citrin) - * Rework IP addresses in upstreams: - - Select ipv4/unix addresses if they exist and use ipv6 for ipv6 only - upstreams (since the support of ipv6 is poor in many OSes and - environments) - - Free IP list on upstream destruction - - Add test cases for addresses selection - - Allow adding of free form IP addresses to upstreams + * Fix build under *BSD + * Detect HAN unicode script + * Implement language detection heuristic for text parts + * Fix time output in history + * Improve piechart coloring + * Fix \r\n conversion in DKIM module (reported by @citrin) + * Try to detect systems with no IPv6 support + * Fix multiple/single values in use settings (reported by @citrin) + * Rework IP addresses in upstreams: + - Select ipv4/unix addresses if they exist and use ipv6 for ipv6 only + upstreams (since the support of ipv6 is poor in many OSes and + environments) + - Free IP list on upstream destruction + - Add test cases for addresses selection + - Allow adding of free form IP addresses to upstreams * Fix endianness in lua_radix search (reported by @citrin) * Soft shutdown should also set wanna_die flag (reported by @citrin) * Stop use-after-free in event loop termination @@ -3787,26 +3972,26 @@ * Fix issues with PTR and MX elements in SPF parser (reported by @citrin) 0.7.3: - * New upstreams code: - - simplify upstreams API; - - unify strings parsing in upstreams definition; - - add configuration options for the upstreams; - - for failed upstreams re-resolve their addresses; - - use all resolved addresses for an upstream (round-robin); - - implement stable hashing and use it by default for upstreams; - - add unit test for upstreams module. - * Rework signals processing in all rspamd workers: - - signals are now processed in the event loop; - - implement the most common signal handlers for all workers; - - add callbacks for workers specific signal handlers - * Fix critical issue with fuzzy storage: - Fuzzy stroage could not save any hashes on termination due to bugged - signals handling + * New upstreams code: + - simplify upstreams API; + - unify strings parsing in upstreams definition; + - add configuration options for the upstreams; + - for failed upstreams re-resolve their addresses; + - use all resolved addresses for an upstream (round-robin); + - implement stable hashing and use it by default for upstreams; + - add unit test for upstreams module. + * Rework signals processing in all rspamd workers: + - signals are now processed in the event loop; + - implement the most common signal handlers for all workers; + - add callbacks for workers specific signal handlers + * Fix critical issue with fuzzy storage: + Fuzzy stroage could not save any hashes on termination due to bugged + signals handling * Fix roll history IP storage * Rework ipv4/ipv6 handling in parsing addresses: - - turn off support of IPV6_V6ONLY socket option; - - create ipv6 socket prior to ipv4 one to handle systems with v6/v4 - sockets enabled (Linux) + - turn off support of IPV6_V6ONLY socket option; + - create ipv6 socket prior to ipv4 one to handle systems with v6/v4 + sockets enabled (Linux) * Remove CBL as it's wholly included in Spamhaus XBL (by @fatalbanana) * Remove nszones.com fake RBL (by @citrin) * Fix upstreams interaction for fuzzy_check @@ -3816,21 +4001,21 @@ * Sync bugfixes from libucl 0.7.2: - * Convert all maps to the compressed radix trie - * Allow IPv6 addresses in IP maps - * Remove dynamic items support from symbols cache - * Allow hex encoded output of strings - * Fix bug with control connections count - * Process fuzzy weight correctly (reported by @fatalbanana) - * Remove extra reference retain of http connection on error - * Remove deprecated options from the default config - * Add `one_shot` attr to metric's symbols - * Doc: add documentation for metrics - * Add Upstart job to debian packaging (by @CameronNemo) - * Config: improve SURBL symbols descriptions (by @citrin) - * Config: reflect SURBL changes (by @citrin): - - Outblaze removed, malware moved to separate list: - http://www.surbl.org/news/internal/MW-malware-sublist-added-to-multi + * Convert all maps to the compressed radix trie + * Allow IPv6 addresses in IP maps + * Remove dynamic items support from symbols cache + * Allow hex encoded output of strings + * Fix bug with control connections count + * Process fuzzy weight correctly (reported by @fatalbanana) + * Remove extra reference retain of http connection on error + * Remove deprecated options from the default config + * Add `one_shot` attr to metric's symbols + * Doc: add documentation for metrics + * Add Upstart job to debian packaging (by @CameronNemo) + * Config: improve SURBL symbols descriptions (by @citrin) + * Config: reflect SURBL changes (by @citrin): + - Outblaze removed, malware moved to separate list: + http://www.surbl.org/news/internal/MW-malware-sublist-added-to-multi * Fix C modules initialization on restart * Treat single IP as a single IP in radix lists (reported by @citrin) * Do not touch file and core limits if not asked explicitly (reported by @citrin) @@ -3842,478 +4027,478 @@ * Sync with libucl include features 0.7.1: - * Fix typo in stat output. - * Fix issues with includes crossing with the system includes - * Restore testing framework - * Add radix trie test suite - * Implement new path-compressed radix trie. - - The performance benefit over the old algorithm is about 1.5 times. - - Memory usage is significantly lower as well. - - Now radix trie can accept any IPv4/IPv6 values - * Various improvements to the memory pools code - * Fix writing reply to a client when no filters are defined - * Write base32 encoded fuzzy - * Fix 'soft reject' action - * Fix rspamd reload and modules reconfiguration - * Fix subject rewriting for the default subject - * Fix states for processing task and pre-filters - * Fix issues with connection closing - * Fix crashes in rdns - * Fix ratelimit pre-filter - * Update exim patch. - - Update to the recent exim version - - Strip extra leading src/ from the patch - - Remove sendfile since it was broken - - Fix rspamd spam report for exim - * Improve documentation + * Fix typo in stat output. + * Fix issues with includes crossing with the system includes + * Restore testing framework + * Add radix trie test suite + * Implement new path-compressed radix trie. + - The performance benefit over the old algorithm is about 1.5 times. + - Memory usage is significantly lower as well. + - Now radix trie can accept any IPv4/IPv6 values + * Various improvements to the memory pools code + * Fix writing reply to a client when no filters are defined + * Write base32 encoded fuzzy + * Fix 'soft reject' action + * Fix rspamd reload and modules reconfiguration + * Fix subject rewriting for the default subject + * Fix states for processing task and pre-filters + * Fix issues with connection closing + * Fix crashes in rdns + * Fix ratelimit pre-filter + * Update exim patch. + - Update to the recent exim version + - Strip extra leading src/ from the patch + - Remove sendfile since it was broken + - Fix rspamd spam report for exim + * Improve documentation 0.7.0: - * Use HTTP protocol for all operatiosn - * Webui worker is now removed and controller works as webui - * Allow to serve static files via controller by option `static_dir` - * Rspamd interface is now a part of rspamd - * Rspamc client has been rewritten to use HTTP and non-blocking mode - allowing to start multiple operations simultaneously (see `-n` option) - * Lua API was completely reworked to satisfy modern standards of LUA: - * Module `lua-message` was removed - * Reduced number of superglobals registered by rspamd - * Many functions has been redesigned - * Symbols registration is now more convenient - * Users settings has been rewritten as lua plugin - * Reworked headers system as gmime's based one misses many headers and is - very slow to get headers values - * Reorganized code and removed embedded jannsson by using UCL for all json - parsing - * Migrated to `librdns` for DNS resolving that improves concurrency for - DNS requests significantly - * Fixed tonns of bugs in MIME processing - * Improved metrcis and default symbol's weights - * Added new RBL's - * Fixed a number of issues in the modules - * Removed several memory leaks found - * Fix unicode processing - * Fix fuzzy checking for unicode parts - * Significantly improve documentation and especially LUA API docs - * See migration notes at https://rspamd.com/doc/migration.html + * Use HTTP protocol for all operatiosn + * Webui worker is now removed and controller works as webui + * Allow to serve static files via controller by option `static_dir` + * Rspamd interface is now a part of rspamd + * Rspamc client has been rewritten to use HTTP and non-blocking mode + allowing to start multiple operations simultaneously (see `-n` option) + * Lua API was completely reworked to satisfy modern standards of LUA: + * Module `lua-message` was removed + * Reduced number of superglobals registered by rspamd + * Many functions has been redesigned + * Symbols registration is now more convenient + * Users settings has been rewritten as lua plugin + * Reworked headers system as gmime's based one misses many headers and is + very slow to get headers values + * Reorganized code and removed embedded jannsson by using UCL for all json + parsing + * Migrated to `librdns` for DNS resolving that improves concurrency for + DNS requests significantly + * Fixed tonns of bugs in MIME processing + * Improved metrcis and default symbol's weights + * Added new RBL's + * Fixed a number of issues in the modules + * Removed several memory leaks found + * Fix unicode processing + * Fix fuzzy checking for unicode parts + * Significantly improve documentation and especially LUA API docs + * See migration notes at https://rspamd.com/doc/migration.html 0.6.8: - * Controller now listen for localhost and not for 127.0.0.1 by default - * Allow FCrDNS-style RBL lookups (by @fatalbanana) - * Reduce threshold for parts_differ function. - * Fix hostname lookup for rdns rbl (by @AlexeySa) - * Fix HFILTER_URL_ONELINE to reduce false positive rate. - * Fix whitelist module. - * Allow override system predefined settings without touching system ones - by .try_include macro (by @andrejzverev) - * Check for [ip.address]-style HELO and suppress lookups. (by - @fatalbanana) - * Optimize hfilter (by @AlexeySa) - * Fix issue with random numbers generator in dns. - * Use more clever time values to setup entropy. - * Synced with the recent libucl. + * Controller now listen for localhost and not for 127.0.0.1 by default + * Allow FCrDNS-style RBL lookups (by @fatalbanana) + * Reduce threshold for parts_differ function. + * Fix hostname lookup for rdns rbl (by @AlexeySa) + * Fix HFILTER_URL_ONELINE to reduce false positive rate. + * Fix whitelist module. + * Allow override system predefined settings without touching system ones + by .try_include macro (by @andrejzverev) + * Check for [ip.address]-style HELO and suppress lookups. (by + @fatalbanana) + * Optimize hfilter (by @AlexeySa) + * Fix issue with random numbers generator in dns. + * Use more clever time values to setup entropy. + * Synced with the recent libucl. 0.6.7: - * Use ChaCha20 for DNS generator (more secure DNS id) - * Unknown symbols now has zero weight and not 1.0 - * Fix fuzzy hashes expire time - * Fix critical issue in statfiles on FreeBSD 9 (and some other platforms) - * Add .include_map macro to ucl parser - * Update libucl - * Fix headers end detection for DKIM module - * Fix a bug in received headers parser - * Validate IP addresses before pushing them to lua - * Start new documentation project - * Fixed tonns of other minor bugs - * Start to prepare for 0.7 with HTTP protocol and new settings + * Use ChaCha20 for DNS generator (more secure DNS id) + * Unknown symbols now has zero weight and not 1.0 + * Fix fuzzy hashes expire time + * Fix critical issue in statfiles on FreeBSD 9 (and some other platforms) + * Add .include_map macro to ucl parser + * Update libucl + * Fix headers end detection for DKIM module + * Fix a bug in received headers parser + * Validate IP addresses before pushing them to lua + * Start new documentation project + * Fixed tonns of other minor bugs + * Start to prepare for 0.7 with HTTP protocol and new settings 0.6.6: - * Removed issue with BUFSIZ limitation in the controller output - * Simplify logging symbols escaping - * Adjusted weights for several rules - * Improve spamhaus rbl support - * Removed PBL for received headers checks - * Added hfilter module that performs various HELO and IP checks. - * Rspamd can now be reloaded by HUP signal - * Fuzzy storage should expire hashes properly - * Build system has been reworked for better supportof pkg-config - * Various minor bugfixes + * Removed issue with BUFSIZ limitation in the controller output + * Simplify logging symbols escaping + * Adjusted weights for several rules + * Improve spamhaus rbl support + * Removed PBL for received headers checks + * Added hfilter module that performs various HELO and IP checks. + * Rspamd can now be reloaded by HUP signal + * Fuzzy storage should expire hashes properly + * Build system has been reworked for better supportof pkg-config + * Various minor bugfixes 0.6.5: - * Fixed critical bug in DNS resolver, introduced in 0.6.4 - * Improved multimap and rbl plugins to skip - * Add dns_sockets option for tuning sockets per server in DNS resolver - * Improved packages for rspamd + * Fixed critical bug in DNS resolver, introduced in 0.6.4 + * Improved multimap and rbl plugins to skip + * Add dns_sockets option for tuning sockets per server in DNS resolver + * Improved packages for rspamd 0.6.4: - * Added io channels for DNS request to balance load and reduce id - collisions chance - * Fixed a bug in SPF filter that may cause core dump in specific - circumstances - * FIxed default config for rbl module - * It is possible to get a list of rspamc commands with their descriptions - * Added SORBS bl to the default config - * 2tld file for surbl module has been significantly extended - * Perl modules has been removed from the code. - * Fixed an issue in libucl when parsing macros + * Added io channels for DNS request to balance load and reduce id + collisions chance + * Fixed a bug in SPF filter that may cause core dump in specific + circumstances + * FIxed default config for rbl module + * It is possible to get a list of rspamc commands with their descriptions + * Added SORBS bl to the default config + * 2tld file for surbl module has been significantly extended + * Perl modules has been removed from the code. + * Fixed an issue in libucl when parsing macros 0.6.3: - * Fixed issues with DNS: - - labels decompression algorithm was fixed; - - added resolve_mx to lua API; - - fixed modules that use DNS. - * Lua modules once_received and emails reworked for new resolver API and UCL. - * Debian package was polished. - * Fixed a bug in fuzzy_check module that prevents correct processing messages - without valid parts. + * Fixed issues with DNS: + - labels decompression algorithm was fixed; + - added resolve_mx to lua API; + - fixed modules that use DNS. + * Lua modules once_received and emails reworked for new resolver API and UCL. + * Debian package was polished. + * Fixed a bug in fuzzy_check module that prevents correct processing messages + without valid parts. 0.6.2: - * Fuzzy check module has been reworked: - - now fuzzy_check operates with a group of rules, that define which - servers sre to be checked; - - it is possible to specify read_only groups to avoid learning errors; - - turn fuzzy_check to one_shot mode permanently; - - fuzzy_check module configuration is now incompatible with the previous - versions. - * Imported bugfixes from libucl. - * Fixed whitelist plugin. - * Fixed statfiles resizing. - * Improved logging initialization order. - * Fixed race condition in the controller worker. + * Fuzzy check module has been reworked: + - now fuzzy_check operates with a group of rules, that define which + servers sre to be checked; + - it is possible to specify read_only groups to avoid learning errors; + - turn fuzzy_check to one_shot mode permanently; + - fuzzy_check module configuration is now incompatible with the previous + versions. + * Imported bugfixes from libucl. + * Fixed whitelist plugin. + * Fixed statfiles resizing. + * Improved logging initialization order. + * Fixed race condition in the controller worker. 0.6.1: - * Critical bugfixes: - - fixed build system; - - fixed in_class setting in bayes learning; + * Critical bugfixes: + - fixed build system; + - fixed in_class setting in bayes learning; 0.6.0: - * Use UCL instead xml for configuration (https://github.com/vstakhov/libucl) - * Fix statistics module normalization - * Rework the many modules for the new configuration: - - surbl has incompatible configuration; - - fuzzy_check has incompatible configuration; - - multimap has now new configuration; - - received_rbl is removed and replaced by rbl module. - * Removed deprecated options: - - statfile_pool_size; - - action and required/reject score for a metric. - * Simplify build system and unify configuration for all platforms. - * Improved URL detector (reduced false positive rate). - * Lua 5.2 is now the default and fully supported version. - * Tons of bugfixes and minor improvements. + * Use UCL instead xml for configuration (https://github.com/vstakhov/libucl) + * Fix statistics module normalization + * Rework the many modules for the new configuration: + - surbl has incompatible configuration; + - fuzzy_check has incompatible configuration; + - multimap has now new configuration; + - received_rbl is removed and replaced by rbl module. + * Removed deprecated options: + - statfile_pool_size; + - action and required/reject score for a metric. + * Simplify build system and unify configuration for all platforms. + * Improved URL detector (reduced false positive rate). + * Lua 5.2 is now the default and fully supported version. + * Tons of bugfixes and minor improvements. 0.5.6: - * Fix bug with counters incrementing in rolling history - * Detect expl and exp2l as some systems do not have it - * Support input streams without Content-Length - * Implement counters output via rspamc and controller interface - * Fix bug with udp sockets in fuzzy storage + * Fix bug with counters incrementing in rolling history + * Detect expl and exp2l as some systems do not have it + * Support input streams without Content-Length + * Implement counters output via rspamc and controller interface + * Fix bug with udp sockets in fuzzy storage 0.5.5: - * New bayes normalizator based on inverse chi-square function - * Various fixes to fuzzy storage - * Allow update fuzzy storage only from specific IP addresses - * Better support of IPv6 and address selection algorithms - * Add CentOS spec file + * New bayes normalizator based on inverse chi-square function + * Various fixes to fuzzy storage + * Allow update fuzzy storage only from specific IP addresses + * Better support of IPv6 and address selection algorithms + * Add CentOS spec file 0.5.4: - * Fixed issues with diff algorithm - * Added support of RRD statistics - * Add webui worker for interface interaction - * Fix a lot of issues with dynamic conf - * Fix critical memory leak in settings code - * Improve stability of the system + * Fixed issues with diff algorithm + * Added support of RRD statistics + * Add webui worker for interface interaction + * Fix a lot of issues with dynamic conf + * Fix critical memory leak in settings code + * Improve stability of the system 0.5.3: - * Added dynamic options - * Added advanced metaclassfication - * Added RESTfull API for controller - * Improved hashing algorithms - * Various fixes for rspamc client: - - allow interacting with unix sockets - - librspamdclient major cleanup - - bayes is now default classifier + * Added dynamic options + * Added advanced metaclassfication + * Added RESTfull API for controller + * Improved hashing algorithms + * Various fixes for rspamc client: + - allow interacting with unix sockets + - librspamdclient major cleanup + - bayes is now default classifier 0.5.2: - * Added lua bindings for: - - basic mime parts, that allows checkign attachements for example; - - DNS resolver; - * Existing lua bindings now works without task object allowing to use them - in custom code. - * Threads system was reworked to avoid global lua interpreter lock. - * DKIM module now converts all line endings to CRLF how opendkim does. - * URL detector is now more accurate for text parts. - * Several critical bugs and memory leaks were fixed. + * Added lua bindings for: + - basic mime parts, that allows checkign attachements for example; + - DNS resolver; + * Existing lua bindings now works without task object allowing to use them + in custom code. + * Threads system was reworked to avoid global lua interpreter lock. + * DKIM module now converts all line endings to CRLF how opendkim does. + * URL detector is now more accurate for text parts. + * Several critical bugs and memory leaks were fixed. 0.5.1: - * Added lua worker type to handle network connections in lua. - * Added lua bindings for async_session, IO dispatcher, memory_pool, and - worker. - * Composites can now uses other composites in expressions. - * Fixes for debian package and for FreeBSD >= 9.1 - * Add support of gmime-2.6 if gmime-2.4 is not found. - * Improve url detection and phishing detection. - * Add lua mime_part library to get an access to all message part - attributes (like filename, length, type). + * Added lua worker type to handle network connections in lua. + * Added lua bindings for async_session, IO dispatcher, memory_pool, and + worker. + * Composites can now uses other composites in expressions. + * Fixes for debian package and for FreeBSD >= 9.1 + * Add support of gmime-2.6 if gmime-2.4 is not found. + * Improve url detection and phishing detection. + * Add lua mime_part library to get an access to all message part + attributes (like filename, length, type). 0.5.0: - * Added SMTP lightweight balancing proxy with XCLIENT support. - * Added lua bindings for upstreams objects and API. - * New pre-filters are implemented to support initial checking for messages. - * Added ratelimit plugin that uses redis protocol to store data. - * Added ipv6 support to spf and some other modules. - * Unbreak spf plugin. - * Allow options with the same name be threated as list. - * DKIM plugin an parsing code was added. - * Separate build system to put logic in several shared libraries. - * Many bugfixes. + * Added SMTP lightweight balancing proxy with XCLIENT support. + * Added lua bindings for upstreams objects and API. + * New pre-filters are implemented to support initial checking for messages. + * Added ratelimit plugin that uses redis protocol to store data. + * Added ipv6 support to spf and some other modules. + * Unbreak spf plugin. + * Allow options with the same name be threated as list. + * DKIM plugin an parsing code was added. + * Separate build system to put logic in several shared libraries. + * Many bugfixes. 0.3.2: - * Add error handling for regexps - * Fix quit command in controller interface - * Write symbols weights to rspamc output - * Improve logic of selecting rspamc version - * Do not try to parse broken DNS replies - * Add 'raw' flag to FROM_EXCESS_BASE64 rule (requested by citrin) - * Output message id in rspamc reply - * Fix inserting composite symbol - * Fix output of log line - * Document composites - * Add logging for fuzzy checks - * Add logging for learning - * Improve logic of learning messages - do not learn more than specific threshold - * Fix inserting results for symbols that were incorrectly (for example more than 1 time) defined in config file - * Do not output control characters if output is not terminal - * Fix some logic errors in learning - * Consider lua plugins errors as fatal configuration errors - * Fix writing message id during fuzzy_add command - * Display weight of symbols correctly - * Fixes to winnow learning - * One more try to improve accuracy of winnow algorithm - * Add bayesian classifier (initial version) - * Remove normalizer as it is winnow specific thing, so all statistic algorithms now returns value from 0 to 1 - * Some fixes to fuzzy hashes expiration: - * Fix assertion while look up value in NULL hash (found by cirtin) - * Fix normalization for systems that have not tanhl function - * Ignore rfc822 group addresses - * Move images library to core rspamd - * Add lua api to access images properties - * Add post filters to lua API - filters that would be called after all message's processing - * Add ability to check for specified symbol in task results from lua - * Add ability to check for metric's results from lua - * Add ability to learn specified statfile form lua - * Add ability to extract filename and size of images from lua - * Fix assertion while extracting internet address - * Fix races in fuzzy storage - * Make spf parser case insensitive - * Add ability to check hashes of selected mime types - * Add ability to set minimum size in bytes for mime types - * Add ability to set minimum dimensions for images - * Assume all text/* content types as text/plain - * Fix getting data wrapper for gmime24 - * Many fixes to statfile synchronization system - * Fixed statfile pool initialization and synchronization with disk - * Prepare 0.3.2 - * Fix Mail::Rspamd::Config for new rspamd features - * Use Mail::Rspamd::Config in rspamc client - * Write user's name to rspamd log - * Prepare rspamd build infrastructure for creating rpm and deb packages - * Fix depends - * Add start script for linux systems - * Fix shared usage of statfiles - * Add invalidation of statfiles in case of learning, so now statfiles - * This should fix shared usage of statfile pool by several processes - * Fix misprint (reported by az) - * Fix stupid error when all checks can be done in a single pass - * New trie based url scanner (based on libcamel) - * Small fixes to rspamd perl client - * Write fuzzy hashes info to log - * Add trie interface to lua api - * Explain sample config and cleanup it + * Add error handling for regexps + * Fix quit command in controller interface + * Write symbols weights to rspamc output + * Improve logic of selecting rspamc version + * Do not try to parse broken DNS replies + * Add 'raw' flag to FROM_EXCESS_BASE64 rule (requested by citrin) + * Output message id in rspamc reply + * Fix inserting composite symbol + * Fix output of log line + * Document composites + * Add logging for fuzzy checks + * Add logging for learning + * Improve logic of learning messages - do not learn more than specific threshold + * Fix inserting results for symbols that were incorrectly (for example more than 1 time) defined in config file + * Do not output control characters if output is not terminal + * Fix some logic errors in learning + * Consider lua plugins errors as fatal configuration errors + * Fix writing message id during fuzzy_add command + * Display weight of symbols correctly + * Fixes to winnow learning + * One more try to improve accuracy of winnow algorithm + * Add bayesian classifier (initial version) + * Remove normalizer as it is winnow specific thing, so all statistic algorithms now returns value from 0 to 1 + * Some fixes to fuzzy hashes expiration: + * Fix assertion while look up value in NULL hash (found by cirtin) + * Fix normalization for systems that have not tanhl function + * Ignore rfc822 group addresses + * Move images library to core rspamd + * Add lua api to access images properties + * Add post filters to lua API - filters that would be called after all message's processing + * Add ability to check for specified symbol in task results from lua + * Add ability to check for metric's results from lua + * Add ability to learn specified statfile form lua + * Add ability to extract filename and size of images from lua + * Fix assertion while extracting internet address + * Fix races in fuzzy storage + * Make spf parser case insensitive + * Add ability to check hashes of selected mime types + * Add ability to set minimum size in bytes for mime types + * Add ability to set minimum dimensions for images + * Assume all text/* content types as text/plain + * Fix getting data wrapper for gmime24 + * Many fixes to statfile synchronization system + * Fixed statfile pool initialization and synchronization with disk + * Prepare 0.3.2 + * Fix Mail::Rspamd::Config for new rspamd features + * Use Mail::Rspamd::Config in rspamc client + * Write user's name to rspamd log + * Prepare rspamd build infrastructure for creating rpm and deb packages + * Fix depends + * Add start script for linux systems + * Fix shared usage of statfiles + * Add invalidation of statfiles in case of learning, so now statfiles + * This should fix shared usage of statfile pool by several processes + * Fix misprint (reported by az) + * Fix stupid error when all checks can be done in a single pass + * New trie based url scanner (based on libcamel) + * Small fixes to rspamd perl client + * Write fuzzy hashes info to log + * Add trie interface to lua api + * Explain sample config and cleanup it 0.3.1: - * Add modules documentation - * Continue implementing smtp proxy - * Implement new learning system, now rspamd should be much more intelligent while learning messages - * Convert statistic sums to use long double for counters - * Use hyperbolic tangent for internal normalizer - * In classify normalize result after comparing, not before - * New symbols sorter - * Fix strict aliasing while compiling with optimization - * Fix tanhl detection for platforms that have not implementation of it - * Remove several compile warnings - * Add experimental support of dynamic rules to regexp module - * Document views configuration - * Several fixes to documentation - * Add more logic for dynamic rules - * Add documentation for dynamic rules - * Add ability to make negations in networks in dynamic rules - * Clean up cache items correctly - * Implement basic SMTP dialog: - * Implement interaction with smtp upstream (with support of XCLIENT) - * Check messages received via smtp proxy - * Add support for sendfile in io dispatcher - * Fix issues with compatibility of worker_task and smtp proxy - * Proxy DATA command - * Fix SMTP - * Change metric logic - * Completely remove lex/yacc readers for config - * Make common sense of metric/action and symbols - * Sync changes with all plugins - * Incorrectly removed in previous commit - * Fix misprint (by Andrej Zverev) - * announce the "password" keyword in usage list - * Implement initial version of greylisting triplets storage - * Fix issues with smtp worker - * Fix QUIT command in SMTP worker - * Some fixes about new metrics system (may be incomplete) - * Get weights of symbol from default metric for symbols cache - * Fix setting task->from/task->rctp in smtp client - * Copy from and rcpt correctly - * Some performance improvements to IO dispatcher (do not drain the whole buffer after a single line readed) - * Fix smtp data input - * Fix misprint - * Add limit of maximum allowed smtp session errors - * New logic of SURBL module: - * Use system mkstemp(3) on systems where it is available as glib implementation - * Try to fix memmove issues in io dispatcher - * Remove debug from SURBL module - * Rewrite buffered input for line policy (again) - * Fix issue with links that are ip addresses in numeric form in surbl - * On Darwin use BSD style sendfile definition - * Reorganize platform specific knobs in CMakeLists - * Use gettimeofday on systems that have not clock_getres - * Use ftime for dns trans id generation on systems without clock_getres - * Darwin sendfile(2) support - * TIMEDB->TIMEB - * More to previous commit - * Pass env from main() arguments instead of platform specific global environ - * Fix compatibility issues - * Fix -lintl detection - * Init some variables to avoid problems - * Remove garbadge (gnome terminal sucks) - * Add more information about why we drop smtp connection - * Fix mkstemp call - * Send to upstream QUIT command at the end of session - * Check return value of each rspamd_dispatcher_write as in case of write errors sessions can be destroyed early - * Fix states in smtp dialog - * Use rspamd_snprintf instead of libc one - * Fix URLS command - * Fix reconfigure process of surbl module - * Fix destroying smtp session (unmap memory and do not delete pool early) - * Delete pool after using its variables - * Delay timer must be registered in async session to correctly handle connection termination - * Register dns requests in session too - * Make session before registering events - * Remove events in handlers - * Add ability to set filters for smtp worker for each smtp stage - * Add very initial version of DNS resolver (many things to be done) - * Announce weights and sync - * Fix few typo - * Understand short names of facility in logging config - * Add ability to make whitelist for spf checks - * Misprint != -> == - * Handle lua tag in way that it is not required to write additional text: - * Strip all starting whitespace symbols from xml texts - * Fix stupid bug in calculating buffer length while reading file maps - * Add resolv.conf parsing into dns.c - * Fix microseconds<->milliseconds conversions - * Take callback argument in Mail::Rspamd::Client for processing files and directories - * Print results if rspamc is called for a directory - * Fix stupid error with surbl module reconfig (another one, blame me) - * Do not show duplicate urls in url header - * Fix detection of numeric urls (reported by citrin) - * Write real time of message's scan to log (not only virtual) - * Fix chartable module in utf mode - * Fix parsing of some broken urls - * Add ability to test regexp with 'T' flag - * Write more code for DNS resolver: - * Make DNS resolver working - * Many improvements to rspamd test suite: now it CAN be used for testing rspamd functionality - * Write DNS resolver tests - * Fix issues with memory_pool mutexes and with creating of statfiles - * Forgotten in previous commit - * Add support for parsing SPF and SRV records - * Fix PTR parsing - * Add tests - * Make SURBL module to use rspamd dns module - * Several fixes to DNS logic - * Remove evdns and use only rspamd resolver - * Very hard to detect problem with race among error in socket and destroying task while we are writing to socket and go through a hash table - * Fix resolving in smtp module - * Init events before configuring resolver in smtp worker - * Set resolver inside task - * Fix reload signal (reported by citrin) - * Some improvements to redirector - * Call has_forked method to inform POE about fork - * Fix lua DNS code - * Decompress labels in DNS packets more strictly - * Fix some problems with TXT records - * Try to fix removing of DNS events - * Do not insert unparsed RR's into reply - * Calling callbacks may cause destroying session from which we are calling callback so we MUST call callback as the latest action - * Fix check_smtp_data function - * Add ability to make views by recipient - * Add ability to set metric's action from config file - * Fix bug with writing garbadge if message has no urls or no messages - * Fix bug with incorrect behaviour of compare_parts_distance function - * Add ability to assign several actions to one metric - * Report action in rspamc protocol - * Mail::Rspamd::Client and rspamc can now understand Action header too - * Write action to log as well - * Make valgrind happy about comparing symbols - * Add more debug to comparing parts distance function - * Write action even if message has no symbols - * Make improvements to HTML entities decoder: now it replaces entities with common characters and - * Add -d option to force debug output - * Assume 7bit as default transfer encoding - * Do not overwrite lua plugins and configs if they already exists in target directory - * Improve logging - * Write queue id to log - * Remove test messages from cmake - * Reopen log file by USR1 signal - * Add reopenlog method to FreeBSD rc script - * Adopt foreach for cmake 2.6 - * Fix to rc script - * Do not try to resolve names with several dots in a row - * Fix surbl request formatting for ip addresses - * Handle cases of broken requests - * Fix problems with parsing compressed names - * Fix TXT records parsing - * Fix expanding spf macros that may fail in rare cases - * Fix another error with early task destroying - * Handle empty from header - * Improve reopenlog command in rc script - * Strip trailing whitespace characters in Mail::Rspamd::Client - * Use ungreedy match to strip trailing whitespaces - * Stupid error in calculation compressed label length - * Some optimizations to client library - * Do not compare empty parts - * Empty and non-empty parts are always different - * Save in regexp cache the whole regexp with header name (if exists) and with flags - * Add rspamd_log variable to lua plugins to access logging functions - * Each part in rspamd task now can have parent part - * Check for parts distance only for multipart/alternative subparts - * Do not check attachements even if they are text (but attached as file) - * Do not die if write (2) returned ENOSPACE while doing logging, turn on throttling mode instead (1 write try in a second) - * Add ability to turn on debug for specific symbols - * Add ability to configure dns timeouts and dns retransmits in config file - * More debug - * Fix extracting arguments in lua logger interface - * Turn off debug_ip during reload if it was disabled by new config - * Improve lua logging - * Pre-init symbols cache when rereading config - * Fix lua representing of invalid ip (nil, not 255.255.255.255) - * Fix R_TO_SEEMS_AUTO rule (by citrin) - * Add multimap lua plugin - * Fix some multimap issues - * Try to save images hashes to fuzzy storage to stop some annoying spammers - * Allocate some more bytes for read buffer to avoid incorrect behavoiur - * Add ability to check dns black lists by multimap module - * Add multimap documentation - * Fix labels parsing - * Another try to save regexps in cache correctly - * Improve test logs for regexps - * Fix parsing txt records to avoid reading of uninitialized data - * Fix error with writing symbols cache file - * Fix error while working in utf mode when raw regexps was not created properly - * Do not add extra byte while converting text to utf - * Add error handling for regexps + * Add modules documentation + * Continue implementing smtp proxy + * Implement new learning system, now rspamd should be much more intelligent while learning messages + * Convert statistic sums to use long double for counters + * Use hyperbolic tangent for internal normalizer + * In classify normalize result after comparing, not before + * New symbols sorter + * Fix strict aliasing while compiling with optimization + * Fix tanhl detection for platforms that have not implementation of it + * Remove several compile warnings + * Add experimental support of dynamic rules to regexp module + * Document views configuration + * Several fixes to documentation + * Add more logic for dynamic rules + * Add documentation for dynamic rules + * Add ability to make negations in networks in dynamic rules + * Clean up cache items correctly + * Implement basic SMTP dialog: + * Implement interaction with smtp upstream (with support of XCLIENT) + * Check messages received via smtp proxy + * Add support for sendfile in io dispatcher + * Fix issues with compatibility of worker_task and smtp proxy + * Proxy DATA command + * Fix SMTP + * Change metric logic + * Completely remove lex/yacc readers for config + * Make common sense of metric/action and symbols + * Sync changes with all plugins + * Incorrectly removed in previous commit + * Fix misprint (by Andrej Zverev) + * announce the "password" keyword in usage list + * Implement initial version of greylisting triplets storage + * Fix issues with smtp worker + * Fix QUIT command in SMTP worker + * Some fixes about new metrics system (may be incomplete) + * Get weights of symbol from default metric for symbols cache + * Fix setting task->from/task->rctp in smtp client + * Copy from and rcpt correctly + * Some performance improvements to IO dispatcher (do not drain the whole buffer after a single line readed) + * Fix smtp data input + * Fix misprint + * Add limit of maximum allowed smtp session errors + * New logic of SURBL module: + * Use system mkstemp(3) on systems where it is available as glib implementation + * Try to fix memmove issues in io dispatcher + * Remove debug from SURBL module + * Rewrite buffered input for line policy (again) + * Fix issue with links that are ip addresses in numeric form in surbl + * On Darwin use BSD style sendfile definition + * Reorganize platform specific knobs in CMakeLists + * Use gettimeofday on systems that have not clock_getres + * Use ftime for dns trans id generation on systems without clock_getres + * Darwin sendfile(2) support + * TIMEDB->TIMEB + * More to previous commit + * Pass env from main() arguments instead of platform specific global environ + * Fix compatibility issues + * Fix -lintl detection + * Init some variables to avoid problems + * Remove garbadge (gnome terminal sucks) + * Add more information about why we drop smtp connection + * Fix mkstemp call + * Send to upstream QUIT command at the end of session + * Check return value of each rspamd_dispatcher_write as in case of write errors sessions can be destroyed early + * Fix states in smtp dialog + * Use rspamd_snprintf instead of libc one + * Fix URLS command + * Fix reconfigure process of surbl module + * Fix destroying smtp session (unmap memory and do not delete pool early) + * Delete pool after using its variables + * Delay timer must be registered in async session to correctly handle connection termination + * Register dns requests in session too + * Make session before registering events + * Remove events in handlers + * Add ability to set filters for smtp worker for each smtp stage + * Add very initial version of DNS resolver (many things to be done) + * Announce weights and sync + * Fix few typo + * Understand short names of facility in logging config + * Add ability to make whitelist for spf checks + * Misprint != -> == + * Handle lua tag in way that it is not required to write additional text: + * Strip all starting whitespace symbols from xml texts + * Fix stupid bug in calculating buffer length while reading file maps + * Add resolv.conf parsing into dns.c + * Fix microseconds<->milliseconds conversions + * Take callback argument in Mail::Rspamd::Client for processing files and directories + * Print results if rspamc is called for a directory + * Fix stupid error with surbl module reconfig (another one, blame me) + * Do not show duplicate urls in url header + * Fix detection of numeric urls (reported by citrin) + * Write real time of message's scan to log (not only virtual) + * Fix chartable module in utf mode + * Fix parsing of some broken urls + * Add ability to test regexp with 'T' flag + * Write more code for DNS resolver: + * Make DNS resolver working + * Many improvements to rspamd test suite: now it CAN be used for testing rspamd functionality + * Write DNS resolver tests + * Fix issues with memory_pool mutexes and with creating of statfiles + * Forgotten in previous commit + * Add support for parsing SPF and SRV records + * Fix PTR parsing + * Add tests + * Make SURBL module to use rspamd dns module + * Several fixes to DNS logic + * Remove evdns and use only rspamd resolver + * Very hard to detect problem with race among error in socket and destroying task while we are writing to socket and go through a hash table + * Fix resolving in smtp module + * Init events before configuring resolver in smtp worker + * Set resolver inside task + * Fix reload signal (reported by citrin) + * Some improvements to redirector + * Call has_forked method to inform POE about fork + * Fix lua DNS code + * Decompress labels in DNS packets more strictly + * Fix some problems with TXT records + * Try to fix removing of DNS events + * Do not insert unparsed RR's into reply + * Calling callbacks may cause destroying session from which we are calling callback so we MUST call callback as the latest action + * Fix check_smtp_data function + * Add ability to make views by recipient + * Add ability to set metric's action from config file + * Fix bug with writing garbadge if message has no urls or no messages + * Fix bug with incorrect behaviour of compare_parts_distance function + * Add ability to assign several actions to one metric + * Report action in rspamc protocol + * Mail::Rspamd::Client and rspamc can now understand Action header too + * Write action to log as well + * Make valgrind happy about comparing symbols + * Add more debug to comparing parts distance function + * Write action even if message has no symbols + * Make improvements to HTML entities decoder: now it replaces entities with common characters and + * Add -d option to force debug output + * Assume 7bit as default transfer encoding + * Do not overwrite lua plugins and configs if they already exists in target directory + * Improve logging + * Write queue id to log + * Remove test messages from cmake + * Reopen log file by USR1 signal + * Add reopenlog method to FreeBSD rc script + * Adopt foreach for cmake 2.6 + * Fix to rc script + * Do not try to resolve names with several dots in a row + * Fix surbl request formatting for ip addresses + * Handle cases of broken requests + * Fix problems with parsing compressed names + * Fix TXT records parsing + * Fix expanding spf macros that may fail in rare cases + * Fix another error with early task destroying + * Handle empty from header + * Improve reopenlog command in rc script + * Strip trailing whitespace characters in Mail::Rspamd::Client + * Use ungreedy match to strip trailing whitespaces + * Stupid error in calculation compressed label length + * Some optimizations to client library + * Do not compare empty parts + * Empty and non-empty parts are always different + * Save in regexp cache the whole regexp with header name (if exists) and with flags + * Add rspamd_log variable to lua plugins to access logging functions + * Each part in rspamd task now can have parent part + * Check for parts distance only for multipart/alternative subparts + * Do not check attachements even if they are text (but attached as file) + * Do not die if write (2) returned ENOSPACE while doing logging, turn on throttling mode instead (1 write try in a second) + * Add ability to turn on debug for specific symbols + * Add ability to configure dns timeouts and dns retransmits in config file + * More debug + * Fix extracting arguments in lua logger interface + * Turn off debug_ip during reload if it was disabled by new config + * Improve lua logging + * Pre-init symbols cache when rereading config + * Fix lua representing of invalid ip (nil, not 255.255.255.255) + * Fix R_TO_SEEMS_AUTO rule (by citrin) + * Add multimap lua plugin + * Fix some multimap issues + * Try to save images hashes to fuzzy storage to stop some annoying spammers + * Allocate some more bytes for read buffer to avoid incorrect behavoiur + * Add ability to check dns black lists by multimap module + * Add multimap documentation + * Fix labels parsing + * Another try to save regexps in cache correctly + * Improve test logs for regexps + * Fix parsing txt records to avoid reading of uninitialized data + * Fix error with writing symbols cache file + * Fix error while working in utf mode when raw regexps was not created properly + * Do not add extra byte while converting text to utf + * Add error handling for regexps diff --git a/centos/rspamd.spec b/centos/rspamd.spec index b16e070de..d2b493085 100644 --- a/centos/rspamd.spec +++ b/centos/rspamd.spec @@ -16,7 +16,7 @@ License: ASL 2.0 URL: https://rspamd.com BuildRoot: %{_tmppath}/%{name}-%{version}-%{release} BuildRequires: glib2-devel,libevent-devel,openssl-devel,pcre-devel -BuildRequires: cmake,gmime-devel,file-devel,ragel +BuildRequires: cmake,gmime-devel,file-devel,ragel,libunwind-devel %if 0%{?el6} BuildRequires: perl %else @@ -81,11 +81,10 @@ lua. -DCMAKE_SKIP_INSTALL_RPATH=ON \ %endif %if 0%{?fedora_version} >= 22 || 0%{?suse_version} >= 1320 - -DENABLE_LUAJIT=ON \ + -DENABLE_LUAJIT=ON \ %else - -DENABLE_LUAJIT=OFF \ + -DENABLE_LUAJIT=OFF \ %endif - -DENABLE_HIREDIS=ON \ -DLOGDIR=%{_localstatedir}/log/rspamd \ -DEXAMPLESDIR=%{_datadir}/examples/rspamd \ -DPLUGINSDIR=%{_datadir}/rspamd \ @@ -94,7 +93,8 @@ lua. -DNO_SHARED=ON \ -DDEBIAN_BUILD=1 \ -DRSPAMD_GROUP=%{rspamd_group} \ - -DRSPAMD_USER=%{rspamd_user} + -DRSPAMD_USER=%{rspamd_user} \ + -DENABLE_LIBUNWIND=ON %{__make} %{?jobs:-j%jobs} diff --git a/centos/sources/rspamd.init b/centos/sources/rspamd.init index 67bfa15ff..3dd3b99be 100644 --- a/centos/sources/rspamd.init +++ b/centos/sources/rspamd.init @@ -11,7 +11,7 @@ # Description: Rspamd spam filtering daemon process ### END INIT INFO # -# chkconfig: - 85 15 +# chkconfig: - 85 15 # description: rspamd is a spam filtering system # processname: rspamd # config: /etc/rspamd/rspamd.conf @@ -58,11 +58,22 @@ stop() { if [ "$CONSOLETYPE" != "serial" ]; then echo -en "\\033[16G" fi - while rh_status_q - do + STOPTIMEOUT=30 + while [ $STOPTIMEOUT -gt 0 ]; do + rh_status_q || break sleep 1 - echo -n $"." + let STOPTIMEOUT=${STOPTIMEOUT}-1 done + if [ $STOPTIMEOUT -eq 0 ]; then + echo "Timeout error occurred trying to stop Rspamd. Forcefully stop the remaining processes." + killproc $prog -KILL + # Sleep forever after SIGKILL being sent (e.g. UNINT SLEEP) + while rh_status_q + do + sleep 1 + echo -n $"." + done + fi rm -f $lockfile fi echo diff --git a/circle.yml b/circle.yml deleted file mode 100644 index 7efefae4b..000000000 --- a/circle.yml +++ /dev/null @@ -1,22 +0,0 @@ -dependencies: - pre: - - sudo apt-get update -qq || true - - sudo apt-get install -qq cmake gcc lcov libevent-dev libglib2.0-dev libgmime-2.6-dev libluajit-5.1-dev liblua5.1-0-dev libmagic-dev libpcre3-dev libsqlite3-dev libssl-dev luarocks make python-dev ragel redis-server libgd-dev libfann-dev opendkim-tools - - gem install coveralls-lcov - - sudo pip install demjson psutil==5.4.3 robotframework - - cd .. && wget http://download.redis.io/redis-stable.tar.gz && tar xvzf redis-stable.tar.gz && cd redis-stable && make -j`nproc` && sudo cp src/redis-cli /usr/bin/ - - sudo luarocks install luacheck - -test: - override: - - mkdir ../build ; mkdir ../install ; cd ../build - - cmake ../rspamd -DDBDIR=/nana -DENABLE_COVERAGE=ON -DCMAKE_INSTALL_PREFIX=../install -DENABLE_HIREDIS=ON - - make install -j`nproc` - - make rspamd-test -j`nproc` - - test/rspamd-test -p /rspamd/lua - - RSPAMD_INSTALLROOT=../install sudo -E robot -x xunit.xml --exclude isbroken ../rspamd/test/functional/cases - - lcov --no-external -b ../rspamd -d ../rspamd -c --output-file coverage.info - - if [ ! -z $COVERALLS_REPO_TOKEN ]; then coveralls-lcov -t ${COVERALLS_REPO_TOKEN} coverage.info || true; fi - post: - - for i in output.xml log.html report.html; do mv $i $CIRCLE_ARTIFACTS; done - - mkdir -p $CIRCLE_TEST_REPORTS/rspamd ; mv xunit.xml $CIRCLE_TEST_REPORTS/rspamd diff --git a/conf/dmarc_whitelist.inc b/conf/dmarc_whitelist.inc index 989bd418e..ca8c2148d 100644 --- a/conf/dmarc_whitelist.inc +++ b/conf/dmarc_whitelist.inc @@ -3,6 +3,7 @@ 4chan.org adp.com +advice.hmrc.gov.uk airbnb.com airtel.in alibaba.com @@ -29,9 +30,11 @@ force.com garant.ru gosuslugi.ru hh.ru +hmrc.gov.uk instagram.com linkedin.com livejournal.com +lufthansa-group.com mackeeper.com megafon.ru mercadolibre.com.ar @@ -46,6 +49,7 @@ paypal.com pch.com pinterest.com rostelecom.ru +rt.ru sberbank.ru sportmaster.ru squarespace.com @@ -56,6 +60,7 @@ uber.com ulmart.ru ups.com usps.com +utair.ru verizonwireless.com vk.com vkrugudruzei.ru diff --git a/conf/modules.d/clickhouse.conf b/conf/modules.d/clickhouse.conf index 3dbc3b60c..c35352a03 100644 --- a/conf/modules.d/clickhouse.conf +++ b/conf/modules.d/clickhouse.conf @@ -51,6 +51,18 @@ clickhouse { #dmarc_allow_symbols = ["DMARC_POLICY_ALLOW"]; #dmarc_reject_symbols = ["DMARC_POLICY_REJECT", "DMARC_POLICY_QUARANTINE"]; + #retention { + # # disabled by default + # enable = true; + # # drop | detach, please refer to ClickHouse docs for details + # # http://clickhouse-docs.readthedocs.io/en/latest/query_language/queries.html#manipulations-with-partitions-and-parts + # method = "drop"; + # # how many month the data should be kept in ClickHouse + # period_months = 3; + # # how often run the cleanup process + # run_every = "7d"; + #} + .include(try=true,priority=5) "${DBDIR}/dynamic/clickhouse.conf" .include(try=true,priority=1,duplicate=merge) "$LOCAL_CONFDIR/local.d/clickhouse.conf" .include(try=true,priority=10) "$LOCAL_CONFDIR/override.d/clickhouse.conf" diff --git a/conf/modules.d/fuzzy_check.conf b/conf/modules.d/fuzzy_check.conf index 4dc7b2994..2569a6881 100644 --- a/conf/modules.d/fuzzy_check.conf +++ b/conf/modules.d/fuzzy_check.conf @@ -19,7 +19,7 @@ fuzzy_check { retransmits = 1; rule "rspamd.com" { algorithm = "mumhash"; - servers = "fuzzy.rspamd.com:11335"; + servers = "round-robin:fuzzy1.rspamd.com:11335,fuzzy2.rspamd.com:11335"; encryption_key = "icy63itbhhni8bq15ntp5n5symuixf73s1kpjh6skaq4e7nx5fiy"; symbol = "FUZZY_UNKNOWN"; mime_types = ["*"]; diff --git a/conf/modules.d/mid.conf b/conf/modules.d/mid.conf index db1a5dafb..589ce746a 100644 --- a/conf/modules.d/mid.conf +++ b/conf/modules.d/mid.conf @@ -16,8 +16,9 @@ mid = { source = { url = [ - "${CONFDIR}/mid.inc", - "$LOCAL_CONFDIR/local.d/mid.inc" + "https://maps.rspamd.com/rspamd/mid.inc.zst", + "$LOCAL_CONFDIR/local.d/mid.inc", + "fallback+file://${CONFDIR}/mid.inc" ]; } diff --git a/conf/modules.d/mime_types.conf b/conf/modules.d/mime_types.conf index 04117e543..7bbc0aa08 100644 --- a/conf/modules.d/mime_types.conf +++ b/conf/modules.d/mime_types.conf @@ -15,8 +15,9 @@ mime_types { file = [ - "${CONFDIR}/mime_types.inc", - "${DBDIR}/mime_types.inc.local" + "https://maps.rspamd.com/rspamd/mime_types.inc.zst", + "${DBDIR}/mime_types.inc.local", + "fallback+file://${CONFDIR}/mime_types.inc" ] # Match specific extensions to specific content types diff --git a/conf/modules.d/phishing.conf b/conf/modules.d/phishing.conf index 61bcfb1ac..dd77832a8 100644 --- a/conf/modules.d/phishing.conf +++ b/conf/modules.d/phishing.conf @@ -21,12 +21,13 @@ phishing { openphish_map = "https://www.openphish.com/feed.txt"; # Disabled by default phishtank_enabled = false; - phishtank_map = "https://rspamd.com/phishtank/online-valid.json.zst"; + phishtank_map = "https://maps.rspamd.com/phishtank/online-valid.json.zst"; # Make exclusions for known redirectors redirector_domains = [ - "${CONFDIR}/redirectors.inc:REDIRECTOR_FALSE", - "$LOCAL_CONFDIR/local.d/redirectors.inc:LOCAL_REDIRECTOR_FALSE" + "https://maps.rspamd.com/rspamd/redirectors.inc.zst:REDIRECTOR_FALSE", + "$LOCAL_CONFDIR/local.d/redirectors.inc:LOCAL_REDIRECTOR_FALSE", + "fallback+file://${CONFDIR}/redirectors.inc:REDIRECTOR_FALSE" ]; .include(try=true,priority=5) "${DBDIR}/dynamic/phishing.conf" diff --git a/conf/modules.d/rbl.conf b/conf/modules.d/rbl.conf index 8a5b2dab8..f3373c2ae 100644 --- a/conf/modules.d/rbl.conf +++ b/conf/modules.d/rbl.conf @@ -115,6 +115,16 @@ rbl { DNSWL_BLOCKED = "127.0.0.255"; } } + + # Provided by https://virusfree.cz + virusfree { + symbol = "RBL_VIRUSFREE_UNKNOWN"; + rbl = "bip.virusfree.cz"; + ipv6 = true; + returncodes { + RBL_VIRUSFREE_BOTNET = "127.0.0.2"; + } + } } .include(try=true,priority=5) "${DBDIR}/dynamic/rbl.conf" diff --git a/conf/modules.d/reputation.conf b/conf/modules.d/reputation.conf new file mode 100644 index 000000000..412341f2c --- /dev/null +++ b/conf/modules.d/reputation.conf @@ -0,0 +1,35 @@ +# Please don't modify this file as your changes might be overwritten with +# the next update. +# +# You can modify '$LOCAL_CONFDIR/rspamd.conf.local.override' to redefine +# parameters defined on the top level +# +# You can modify '$LOCAL_CONFDIR/rspamd.conf.local' to add +# parameters defined on the top level +# +# For specific modules or configuration you can also modify +# '$LOCAL_CONFDIR/local.d/file.conf' - to add your options or rewrite defaults +# '$LOCAL_CONFDIR/override.d/file.conf' - to override the defaults +# +# See https://rspamd.com/doc/tutorials/writing_rules.html for details + + +# Reputation module is experimental! (you need to enable experimental modules +# explicitly in options by setting `enable_experimental = true`) + +reputation { +# rules { +# SPF_REPUTATION = { +# selector { +# type = "spf"; +# } +# backend { +# type = "redis"; +# } +# symbol = "SPF_REPUTATION"; +# } +# } + .include(try=true,priority=5) "${DBDIR}/dynamic/reputation.conf" + .include(try=true,priority=1,duplicate=merge) "$LOCAL_CONFDIR/local.d/reputation.conf" + .include(try=true,priority=10) "$LOCAL_CONFDIR/override.d/reputation.conf" +} diff --git a/conf/modules.d/surbl.conf b/conf/modules.d/surbl.conf index 446cf616f..34fdf8ed2 100644 --- a/conf/modules.d/surbl.conf +++ b/conf/modules.d/surbl.conf @@ -15,12 +15,14 @@ surbl { whitelist = [ - "${CONFDIR}/surbl-whitelist.inc", - "${DBDIR}/surbl-whitelist.inc.local" + "https://maps.rspamd.com/rspamd/surbl-whitelist.inc.zst", + "${DBDIR}/surbl-whitelist.inc.local", + "fallback+file://${CONFDIR}/surbl-whitelist.inc" ]; exceptions = [ - "${CONFDIR}/2tld.inc", - "${DBDIR}/2tld.inc.local" + "https://maps.rspamd.com/rspamd/2tld.inc.zst", + "${DBDIR}/2tld.inc.local", + "fallback+file://${CONFDIR}/2tld.inc" ]; rules { @@ -48,7 +50,7 @@ surbl { process_script =<<EOD function(url, suffix) local cr = require "rspamd_cryptobox_hash" - h = cr.create(url):base32():sub(1, 32) + local h = cr.create(url):base32():sub(1, 32) return string.format("%s.%s", h, suffix) end EOD; diff --git a/conf/modules.d/whitelist.conf b/conf/modules.d/whitelist.conf index de8b9b516..96c9ed02e 100644 --- a/conf/modules.d/whitelist.conf +++ b/conf/modules.d/whitelist.conf @@ -18,8 +18,9 @@ whitelist { "WHITELIST_SPF" = { valid_spf = true; domains = [ - "${CONFDIR}/spf_whitelist.inc", + #"https://maps.rspamd.com/rspamd/spf_whitelist.inc.zst", # Missing now "${DBDIR}/spf_whitelist.inc.local", + "fallback+file://${CONFDIR}/spf_whitelist.inc" ]; score = -1.0 description = "Mail comes from the whitelisted domain and has a valid SPF policy"; @@ -28,8 +29,9 @@ whitelist { "WHITELIST_DKIM" = { valid_dkim = true; domains = [ - "${CONFDIR}/dkim_whitelist.inc", + #"https://maps.rspamd.com/rspamd/dkim_whitelist.inc.zst", # Missing now "${DBDIR}/dkim_whitelist.inc.local", + "fallback+file://${CONFDIR}/dkim_whitelist.inc" ]; description = "Mail comes from the whitelisted domain and has a valid DKIM signature"; score = -1.0 @@ -38,8 +40,9 @@ whitelist { valid_spf = true; valid_dkim = true; domains = [ - "${CONFDIR}/spf_dkim_whitelist.inc", + "https://maps.rspamd.com/rspamd/spf_dkim_whitelist.inc.zst", "${DBDIR}/spf_dkim_whitelist.inc.local", + "fallback+file://${CONFDIR}/spf_dkim_whitelist.inc" ]; score = -3.0; description = "Mail comes from the whitelisted domain and has valid SPF and DKIM policies"; @@ -47,8 +50,9 @@ whitelist { "WHITELIST_DMARC" = { valid_dmarc = true; domains = [ - "${CONFDIR}/dmarc_whitelist.inc", + "https://maps.rspamd.com/rspamd/dmarc_whitelist.inc.zst", "${DBDIR}/dmarc_whitelist.inc.local", + "fallback+file://${CONFDIR}/dmarc_whitelist.inc" ]; score = -7.0; description = "Mail comes from the whitelisted domain and has valid DMARC and DKIM policies"; diff --git a/conf/redirectors.inc b/conf/redirectors.inc index 44829d577..812f40539 100644 --- a/conf/redirectors.inc +++ b/conf/redirectors.inc @@ -183,6 +183,7 @@ cort.as cortas.elpais.com cot.ag cowurl.com +cp.bitrix.ru cr.am createurl.com crks.me @@ -418,6 +419,7 @@ linkde.info linkee.com link.hhut.ru linkl.ru +link.mail.e.glavbukh-mail.ru link.rengo.ru link.sendsay.ru linkslash.ca diff --git a/conf/scores.d/policies_group.conf b/conf/scores.d/policies_group.conf index b7ca9f6f6..e432053ec 100644 --- a/conf/scores.d/policies_group.conf +++ b/conf/scores.d/policies_group.conf @@ -98,7 +98,7 @@ symbols = { "ARC_REJECT" { weight = 2.0; - description = "ARC checks success"; + description = "ARC checks failed"; groups = ["arc"]; } @@ -119,4 +119,4 @@ symbols = { description = "ARC signature absent"; groups = ["arc"]; } -}
\ No newline at end of file +} diff --git a/conf/scores.d/rbl_group.conf b/conf/scores.d/rbl_group.conf index be145e350..6c48cf3bb 100644 --- a/conf/scores.d/rbl_group.conf +++ b/conf/scores.d/rbl_group.conf @@ -149,4 +149,9 @@ symbols = { weight = 1.0; description = "Address is listed in Spameatingmonkey RBL (ipv6)"; } + + "RBL_VIRUSFREE_BOTNET" { + weight = 2.0; + description = "Source IP is listed in virusfree.cz BL"; + } } diff --git a/conf/spf_dkim_whitelist.inc b/conf/spf_dkim_whitelist.inc index 2759e6b7f..fe0ddbbaa 100644 --- a/conf/spf_dkim_whitelist.inc +++ b/conf/spf_dkim_whitelist.inc @@ -176,6 +176,7 @@ southwest.com spotify.com springer.com squarespace.com +stalker.com steampowered.com stumbleupon.com surveymonkey.com @@ -185,6 +186,7 @@ taleo.net taobao.com target.com taringa.net +taxi.yandex.ru tele2.ru thekitchn.com tokopedia.com diff --git a/config.h.in b/config.h.in index 007586b8f..d6a6a2cbe 100644 --- a/config.h.in +++ b/config.h.in @@ -83,6 +83,7 @@ #cmakedefine HAVE_SETITIMER 1 #cmakedefine HAVE_SETPROCTITLE 1 #cmakedefine HAVE_SETSIG 1 +#cmakedefine HAVE_SIGALTSTACK 1 #cmakedefine HAVE_SIGINFO_H 1 #cmakedefine HAVE_SOCK_SEQPACKET 1 #cmakedefine HAVE_SSL_TLSEXT_HOSTNAME 1 @@ -118,6 +119,7 @@ #cmakedefine HAVE_TANHL 1 #cmakedefine HAVE_TERMIOS_H 1 #cmakedefine HAVE_TIME_H 1 +#cmakedefine HAVE_UCONTEXT_H 1 #cmakedefine HAVE_UNISTD_H 1 #cmakedefine HAVE_VFORK 1 #cmakedefine HAVE_WAIT4 1 @@ -141,6 +143,7 @@ #cmakedefine WITH_SQLITE 1 #cmakedefine WITH_SYSTEM_HIREDIS 1 #cmakedefine WITH_TORCH 1 +#cmakedefine WITH_LIBUNWIND 1 #cmakedefine DISABLE_PTHREAD_MUTEX 1 diff --git a/contrib/librdns/dns_private.h b/contrib/librdns/dns_private.h index 44bb3dd84..8200bf038 100644 --- a/contrib/librdns/dns_private.h +++ b/contrib/librdns/dns_private.h @@ -111,11 +111,17 @@ struct rdns_io_channel { ref_entry_t ref; }; +struct rdns_fake_reply_idx { + enum rdns_request_type type; + unsigned len; + char request[0]; +}; + struct rdns_fake_reply { - char *request; enum dns_rcode rcode; struct rdns_reply_entry *result; UT_hash_handle hh; + struct rdns_fake_reply_idx key; }; diff --git a/contrib/librdns/rdns.h b/contrib/librdns/rdns.h index 545d9421f..7ed35514f 100644 --- a/contrib/librdns/rdns.h +++ b/contrib/librdns/rdns.h @@ -45,6 +45,7 @@ struct rdns_io_channel; typedef void (*dns_callback_type) (struct rdns_reply *reply, void *arg); enum rdns_request_type { + RDNS_REQUEST_INVALID = -1, RDNS_REQUEST_A = 1, RDNS_REQUEST_NS = 2, RDNS_REQUEST_SOA = 6, @@ -111,6 +112,7 @@ struct rdns_reply_entry { enum dns_rcode { + RDNS_RC_INVALID = -1, RDNS_RC_NOERROR = 0, RDNS_RC_FORMERR = 1, RDNS_RC_SERVFAIL = 2, @@ -221,6 +223,8 @@ struct rdns_request_name { unsigned int len; }; +#define MAX_FAKE_NAME 1000 + /* * RDNS API */ @@ -327,11 +331,12 @@ void rdns_resolver_register_plugin (struct rdns_resolver *resolver, * Add a fake reply for a specified name * @param resolver * @param type - * @param name + * @param name (must not be larger than MAX_FAKE_NAME) * @param reply */ void rdns_resolver_set_fake_reply (struct rdns_resolver *resolver, const char *name, + enum rdns_request_type type, enum dns_rcode rcode, struct rdns_reply_entry *reply); diff --git a/contrib/librdns/resolver.c b/contrib/librdns/resolver.c index 7afabb1ba..3abb17304 100644 --- a/contrib/librdns/resolver.c +++ b/contrib/librdns/resolver.c @@ -521,6 +521,9 @@ rdns_process_retransmit (int fd, void *arg) } } +#define align_ptr(p, a) \ + (guint8 *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1)) + struct rdns_request* rdns_make_request_full ( struct rdns_resolver *resolver, @@ -541,13 +544,23 @@ rdns_make_request_full ( const char *cur_name, *last_name = NULL; struct rdns_compression_entry *comp = NULL; struct rdns_fake_reply *fake_rep = NULL; + char fake_buf[MAX_FAKE_NAME + sizeof (struct rdns_fake_reply_idx) + 16]; + struct rdns_fake_reply_idx *idx; if (resolver == NULL || !resolver->initialized) { + if (resolver == NULL) { + return NULL; + } + + rdns_err ("resolver is uninitialized"); + return NULL; } req = malloc (sizeof (struct rdns_request)); if (req == NULL) { + rdns_err ("failed to allocate memory for request: %s", + strerror (errno)); return NULL; } @@ -564,6 +577,9 @@ rdns_make_request_full ( if (req->requested_names == NULL) { free (req); + rdns_err ("failed to allocate memory for request data: %s", + strerror (errno)); + return NULL; } @@ -578,45 +594,60 @@ rdns_make_request_full ( for (i = 0; i < queries * 2; i += 2) { cur = i / 2; cur_name = va_arg (args, const char *); - - if (last_name == NULL) { - HASH_FIND_STR (resolver->fake_elts, cur_name, fake_rep); - - if (fake_rep) { - /* We actually treat it as a short-circuit */ - req->reply = rdns_make_reply (req, fake_rep->rcode); - req->reply->entries = fake_rep->result; - req->state = RDNS_REQUEST_FAKE; - } - } + type = va_arg (args, int); if (cur_name != NULL) { - last_name = cur_name; clen = strlen (cur_name); + if (clen == 0) { - rdns_info ("got empty name to resolve"); + rdns_warn ("got empty name to resolve"); rdns_request_free (req); return NULL; } + + if (last_name == NULL && queries == 1 && clen < MAX_FAKE_NAME) { + /* We allocate structure in the static space */ + idx = (struct rdns_fake_reply_idx *)align_ptr (fake_buf, 16); + idx->type = type; + idx->len = clen; + memcpy (idx->request, cur_name, clen); + HASH_FIND (hh, resolver->fake_elts, idx, sizeof (*idx) + clen, + fake_rep); + + if (fake_rep) { + /* We actually treat it as a short-circuit */ + req->reply = rdns_make_reply (req, fake_rep->rcode); + req->reply->entries = fake_rep->result; + req->state = RDNS_REQUEST_FAKE; + } + } + + last_name = cur_name; tlen += clen; } else if (last_name == NULL) { - rdns_info ("got NULL as the first name to resolve"); + rdns_err ("got NULL as the first name to resolve"); rdns_request_free (req); return NULL; } - if (req->state != RDNS_REQUEST_FAKE && - !rdns_format_dns_name (resolver, last_name, clen, - &req->requested_names[cur].name, &olen)) { - rdns_request_free (req); - return NULL; + if (req->state != RDNS_REQUEST_FAKE) { + if (!rdns_format_dns_name (resolver, last_name, clen, + &req->requested_names[cur].name, &olen)) { + rdns_err ("cannot format %s", last_name); + rdns_request_free (req); + return NULL; + } + + req->requested_names[cur].len = olen; + } + else { + req->requested_names[cur].len = clen; } - type = va_arg (args, int); req->requested_names[cur].type = type; - req->requested_names[cur].len = olen; } + va_end (args); if (req->state != RDNS_REQUEST_FAKE) { @@ -629,12 +660,14 @@ rdns_make_request_full ( type = req->requested_names[i].type; if (queries > 1) { if (!rdns_add_rr (req, cur_name, clen, type, &comp)) { + rdns_err ("cannot add rr", cur_name); REF_RELEASE (req); rnds_compression_free (comp); return NULL; } } else { if (!rdns_add_rr (req, cur_name, clen, type, NULL)) { + rdns_err ("cannot add rr", cur_name); REF_RELEASE (req); rnds_compression_free (comp); return NULL; @@ -692,6 +725,7 @@ rdns_make_request_full ( r = rdns_send_request (req, req->io->sock, true); if (r == -1) { + rdns_info ("cannot send DNS request"); REF_RELEASE (req); return NULL; } @@ -711,10 +745,12 @@ rdns_resolver_init (struct rdns_resolver *resolver) struct rdns_io_channel *ioc; if (!resolver->async_binded) { + rdns_err ("no async backend specified"); return false; } if (resolver->servers == NULL) { + rdns_err ("no DNS servers defined"); return false; } @@ -724,13 +760,16 @@ rdns_resolver_init (struct rdns_resolver *resolver) for (i = 0; i < serv->io_cnt; i ++) { ioc = calloc (1, sizeof (struct rdns_io_channel)); if (ioc == NULL) { - rdns_err ("cannot allocate memory for the resolver"); + rdns_err ("cannot allocate memory for the resolver IO channels"); return false; } + ioc->sock = rdns_make_client_socket (serv->name, serv->port, SOCK_DGRAM); - ioc->active = true; + if (ioc->sock == -1) { - rdns_err ("cannot open socket to %s:%d %s", serv->name, serv->port, strerror (errno)); + ioc->active = false; + rdns_err ("cannot open socket to %s:%d %s", + serv->name, serv->port, strerror (errno)); free (ioc); return false; } @@ -924,35 +963,43 @@ rdns_resolver_set_dnssec (struct rdns_resolver *resolver, bool enabled) void rdns_resolver_set_fake_reply (struct rdns_resolver *resolver, const char *name, + enum rdns_request_type type, enum dns_rcode rcode, struct rdns_reply_entry *reply) { struct rdns_fake_reply *fake_rep; + struct rdns_fake_reply_idx *srch; + unsigned len = strlen (name); - HASH_FIND_STR (resolver->fake_elts, name, fake_rep); + assert (len < MAX_FAKE_NAME); + srch = malloc (sizeof (*srch) + len); + srch->len = len; + srch->type = type; + memcpy (srch->request, name, len); + + HASH_FIND (hh, resolver->fake_elts, srch, len + sizeof (*srch), fake_rep); if (fake_rep) { /* Append reply to the existing list */ fake_rep->rcode = rcode; - DL_APPEND (fake_rep->result, reply); + + if (reply) { + DL_APPEND (fake_rep->result, reply); + } } else { - fake_rep = calloc (1, sizeof (*fake_rep)); + fake_rep = calloc (1, sizeof (*fake_rep) + len); if (fake_rep == NULL) { abort (); } - fake_rep->request = strdup (name); + memcpy (&fake_rep->key, srch, sizeof (*srch) + len); - if (fake_rep->request == NULL) { - abort (); + if (reply) { + DL_APPEND (fake_rep->result, reply); } - fake_rep->rcode = rcode; - DL_APPEND (fake_rep->result, reply); - - HASH_ADD_KEYPTR (hh, resolver->fake_elts, fake_rep->request, - strlen (fake_rep->request), fake_rep); + HASH_ADD (hh, resolver->fake_elts, key, sizeof (*srch) + len, fake_rep); } }
\ No newline at end of file diff --git a/contrib/librdns/util.c b/contrib/librdns/util.c index a4018cbd3..1cace2386 100644 --- a/contrib/librdns/util.c +++ b/contrib/librdns/util.c @@ -286,7 +286,7 @@ rdns_type_fromstr (const char *str) } } - return -1; + return RDNS_REQUEST_INVALID; } enum dns_rcode @@ -334,7 +334,7 @@ rdns_rcode_fromstr (const char *str) } } - return -1; + return RDNS_RC_INVALID; } uint16_t @@ -559,7 +559,7 @@ rdns_resolver_conf_process_line (struct rdns_resolver *resolver, } /* XXX: skip unknown resolv.conf lines */ - return true; + return false; } bool @@ -569,6 +569,7 @@ rdns_resolver_parse_resolv_conf_cb (struct rdns_resolver *resolver, FILE *in; char buf[BUFSIZ]; char *p; + bool processed = false; in = fopen (path, "r"); @@ -588,16 +589,14 @@ rdns_resolver_parse_resolv_conf_cb (struct rdns_resolver *resolver, *p-- = '\0'; } - if (!rdns_resolver_conf_process_line (resolver, buf, cb, ud)) { - rdns_warn ("rdns_resolver_parse_resolv_conf: cannot parse line: %s", buf); - fclose (in); - return false; + if (rdns_resolver_conf_process_line (resolver, buf, cb, ud)) { + processed = true; } } fclose (in); - return true; + return processed; } bool diff --git a/contrib/libucl/khash.h b/contrib/libucl/khash.h index afc3ce3ef..a91db5b2a 100644 --- a/contrib/libucl/khash.h +++ b/contrib/libucl/khash.h @@ -576,6 +576,13 @@ static kh_inline khint_t __ac_Wang_hash(khint_t key) code; \ } } +#define kh_foreach_value_ptr(h, pvvar, code) { khint_t __i; \ + for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ + if (!kh_exist(h,__i)) continue; \ + (pvvar) = &kh_val(h,__i); \ + code; \ + } } + /* More conenient interfaces */ /*! @function diff --git a/contrib/libucl/ucl_util.c b/contrib/libucl/ucl_util.c index 1a3b34293..a65c30cec 100644 --- a/contrib/libucl/ucl_util.c +++ b/contrib/libucl/ucl_util.c @@ -2406,11 +2406,11 @@ ucl_object_insert_key_common (ucl_object_t *top, ucl_object_t *elt, } else { /* Just make a list of scalars */ - DL_APPEND (found, elt); + DL_CONCAT (found, elt); } } else { - DL_APPEND (found, elt); + DL_CONCAT (found, elt); } } diff --git a/contrib/lua-torch/torch7/lib/TH/THGeneral.c b/contrib/lua-torch/torch7/lib/TH/THGeneral.c index 6ad96b51c..f093c422f 100644 --- a/contrib/lua-torch/torch7/lib/TH/THGeneral.c +++ b/contrib/lua-torch/torch7/lib/TH/THGeneral.c @@ -11,16 +11,18 @@ #define __thread __declspec( thread ) #endif -#if (defined(__unix) || defined(_WIN32)) - #if defined(__FreeBSD__) - #include <malloc_np.h> - #else - #include <malloc.h> - #endif -#elif defined(__APPLE__) +#if defined(__APPLE__) #include <malloc/malloc.h> #endif +#if defined(__linux__) +#include <malloc.h> +#endif + +#if defined(__FreeBSD__) +#include <malloc_np.h> +#endif + /* Torch Error Handling */ static void defaultErrorHandlerFunction(const char *msg, void *data) { diff --git a/contrib/t1ha/CMakeLists.txt b/contrib/t1ha/CMakeLists.txt index 6c50d740d..491010ff9 100644 --- a/contrib/t1ha/CMakeLists.txt +++ b/contrib/t1ha/CMakeLists.txt @@ -3,7 +3,7 @@ SET(T1HASRC t1ha1.c ADD_LIBRARY(rspamd-t1ha STATIC ${T1HASRC}) SET_TARGET_PROPERTIES(rspamd-t1ha PROPERTIES VERSION ${RSPAMD_VERSION}) -ADD_DEFINITIONS("-DT1HA_USE_FAST_ONESHOT_READ=1") +ADD_DEFINITIONS("-DT1HA_USE_FAST_ONESHOT_READ=0") IF(ENABLE_FULL_DEBUG MATCHES "OFF") if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") diff --git a/contrib/t1ha/t1ha_bits.h b/contrib/t1ha/t1ha_bits.h index 454e43aed..799737d5f 100644 --- a/contrib/t1ha/t1ha_bits.h +++ b/contrib/t1ha/t1ha_bits.h @@ -1063,11 +1063,13 @@ typedef union t1ha_uint128 { #endif struct { #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ - uint64_t l, h; + uint64_t l; + uint64_t h; #else - uint64_t h, l; + uint64_t h; + uint64_t l; #endif - }; + } p; } t1ha_uint128_t; static __always_inline t1ha_uint128_t not128(const t1ha_uint128_t v) { @@ -1076,8 +1078,8 @@ static __always_inline t1ha_uint128_t not128(const t1ha_uint128_t v) { (defined(_INTEGRAL_MAX_BITS) && _INTEGRAL_MAX_BITS >= 128) r.v = ~v.v; #else - r.l = ~v.l; - r.h = ~v.h; + r.p.l = ~v.p.l; + r.p.h = ~v.p.h; #endif return r; } @@ -1090,8 +1092,8 @@ static __always_inline t1ha_uint128_t left128(const t1ha_uint128_t v, (defined(_INTEGRAL_MAX_BITS) && _INTEGRAL_MAX_BITS >= 128) r.v = v.v << s; #else - r.l = (s < 64) ? v.l << s : 0; - r.h = (s < 64) ? (v.h << s) | (s ? v.l >> (64 - s) : 0) : v.l << (s - 64); + r.p.l = (s < 64) ? v.p.l << s : 0; + r.p.h = (s < 64) ? (v.p.h << s) | (s ? v.p.l >> (64 - s) : 0) : v.p.l << (s - 64); #endif return r; } @@ -1104,8 +1106,8 @@ static __always_inline t1ha_uint128_t right128(const t1ha_uint128_t v, (defined(_INTEGRAL_MAX_BITS) && _INTEGRAL_MAX_BITS >= 128) r.v = v.v >> s; #else - r.l = (s < 64) ? (s ? v.h << (64 - s) : 0) | (v.l >> s) : v.h >> (s - 64); - r.h = (s < 64) ? v.h >> s : 0; + r.p.l = (s < 64) ? (s ? v.p.h << (64 - s) : 0) | (v.p.l >> s) : v.p.h >> (s - 64); + r.p.h = (s < 64) ? v.p.h >> s : 0; #endif return r; } @@ -1117,8 +1119,8 @@ static __always_inline t1ha_uint128_t or128(t1ha_uint128_t x, (defined(_INTEGRAL_MAX_BITS) && _INTEGRAL_MAX_BITS >= 128) r.v = x.v | y.v; #else - r.l = x.l | y.l; - r.h = x.h | y.h; + r.p.l = x.p.l | y.p.l; + r.p.h = x.p.h | y.p.h; #endif return r; } @@ -1130,8 +1132,8 @@ static __always_inline t1ha_uint128_t xor128(t1ha_uint128_t x, (defined(_INTEGRAL_MAX_BITS) && _INTEGRAL_MAX_BITS >= 128) r.v = x.v ^ y.v; #else - r.l = x.l ^ y.l; - r.h = x.h ^ y.h; + r.p.l = x.p.l ^ y.p.l; + r.p.h = x.p.h ^ y.p.h; #endif return r; } @@ -1154,7 +1156,7 @@ static __always_inline t1ha_uint128_t add128(t1ha_uint128_t x, (defined(_INTEGRAL_MAX_BITS) && _INTEGRAL_MAX_BITS >= 128) r.v = x.v + y.v; #else - add64carry_last(add64carry_first(x.l, y.l, &r.l), x.h, y.h, &r.h); + add64carry_last(add64carry_first(x.p.l, y.p.l, &r.p.l), x.p.h, y.p.h, &r.p.h); #endif return r; } @@ -1166,8 +1168,8 @@ static __always_inline t1ha_uint128_t mul128(t1ha_uint128_t x, (defined(_INTEGRAL_MAX_BITS) && _INTEGRAL_MAX_BITS >= 128) r.v = x.v * y.v; #else - r.l = mul_64x64_128(x.l, y.l, &r.h); - r.h += x.l * y.h + y.l * x.h; + r.p.l = mul_64x64_128(x.p.l, y.p.l, &r.p.h); + r.p.h += x.p.l * y.p.h + y.p.l * x.p.h; #endif return r; } diff --git a/debian/control b/debian/control index 5b9693723..799b3b3c0 100644 --- a/debian/control +++ b/debian/control @@ -2,7 +2,7 @@ Source: rspamd Section: mail Priority: extra Maintainer: Mikhail Gusarov <dottedmag@debian.org> -Build-Depends: debhelper (>= 9), dpkg-dev (>= 1.16.1~), cmake, libevent-dev (>= 1.3), libglib2.0-dev (>= 2.16.0), libluajit-5.1-dev [amd64 armel armhf i386 kfreebsd-i386 mips mipsel powerpc powerpcspe] | liblua5.1-dev, libpcre3-dev, libssl-dev (>= 1.0), libcurl4-openssl-dev, libsqlite3-dev, libmagic-dev, perl, dh-systemd, libjemalloc-dev, ragel, libicu-dev, zlib1g-dev +Build-Depends: debhelper (>= 9), dpkg-dev (>= 1.16.1~), cmake, libevent-dev (>= 1.3), libglib2.0-dev (>= 2.16.0), libluajit-5.1-dev [amd64 armel armhf i386 kfreebsd-i386 mips mipsel powerpc powerpcspe] | liblua5.1-dev, libpcre3-dev, libssl-dev (>= 1.0), libcurl4-openssl-dev, libsqlite3-dev, libmagic-dev, perl, dh-systemd, libjemalloc-dev, ragel, libicu-dev, zlib1g-dev, libunwind-dev | libunwind8-dev Standards-Version: 3.9.6 Homepage: https://rspamd.com Vcs-Git: git://github.com/vstakhov/rspamd.git diff --git a/debian/rules b/debian/rules index c958a26ea..5cde81db5 100755 --- a/debian/rules +++ b/debian/rules @@ -27,9 +27,9 @@ override_dh_auto_configure: -DENABLE_FULL_DEBUG=OFF \ -DENABLE_GD=OFF \ -DENABLE_PCRE2=OFF \ - -DENABLE_HIREDIS=ON \ -DENABLE_LUAJIT=ON \ -DENABLE_TORCH=ON \ + -DENABLE_LIBUNWIND=ON \ -DWANT_SYSTEMD_UNITS=ON override_dh_makeshlibs: diff --git a/doc/rspamc.1 b/doc/rspamc.1 index 0100cb41b..338f8053d 100644 --- a/doc/rspamc.1 +++ b/doc/rspamc.1 @@ -1,4 +1,4 @@ -.\" Automatically generated by Pandoc 1.17.2 +.\" Automatically generated by Pandoc 2.2.2.1 .\" .TH "RSPAMC" "1" "" "Rspamd User Manual" "" .hy @@ -7,13 +7,13 @@ \f[C]rspamc\f[] \- rspamd command line client .SH SYNOPSIS .PP -rspamc [\f[I]options\f[]] [\f[I]command\f[]] [\f[I]input\-file\f[]]... +rspamc [\f[I]options\f[]] [\f[I]command\f[]] [\f[I]input\-file\f[]]\&... .PP -rspamc \-\-help +rspamc \[en]help .SH DESCRIPTION .PP -\f[C]rspamc\f[] is a simple client for checking messages using rspamd or -for learning rspamd by messages. +\f[C]rspamc\f[] is a simple rspamd client, primarily for classifying or +learning messages. \f[C]rspamc\f[] supports the following commands: .IP \[bu] 2 Scan commands: @@ -49,17 +49,21 @@ graphs) \f[C]add_action\f[]: add or modify action settings .RE .PP -Control commands that modifies rspamd state are considered as privileged -and basically requires a password to be specified with \f[C]\-P\f[] -option (see \f[B]OPTIONS\f[], below, for details). -This depends on a controller\[aq]s settings and is discussed in -\f[C]rspamd\-workers\f[] page. +Control commands that modify rspamd state are considered privileged and +require a password to be specified with the \f[C]\-P\f[] option (see +\f[B]OPTIONS\f[], below, for details). +.PD 0 +.P +.PD +This depends on a controller's settings and is discussed in the +\f[C]rspamd\-workers\f[] page (see \f[B]SEE ALSO\f[], below, for +details). .PP \f[C]Input\ files\f[] may be either regular file(s) or a directory to scan. If no files are specified \f[C]rspamc\f[] reads from the standard input. -Controller commands usually does not accept any input, however learn* -and fuzzy* commands requires input. +Controller commands usually do not accept any input, however learn* and +fuzzy* commands requires input. .SH OPTIONS .TP .B \-h \f[I]host[:port]\f[], \-\-connect=\f[I]host[:port]\f[] @@ -108,7 +112,8 @@ Emulate that message was received from specified authenticated user .RE .TP .B \-d \f[I]user\@domain\f[], \-\-deliver=\f[I]user\@domain\f[] -Emulate that message is delivered to specified user (for LDA/statistics) +Emulate that message was delivered to specified user (for +LDA/statistics) .RS .RE .TP @@ -134,8 +139,8 @@ MTA) .RE .TP .B \-t \f[I]seconds\f[], \-\-timeout=\f[I]seconds\f[] -Timeout for waiting for a reply (can be floating point number, e.g. -0.1) +Timeout for waiting for a reply (can be floating point number, +e.g.\ 0.1) .RS .RE .TP @@ -197,7 +202,7 @@ This option can be repeated multiple times. .B \-\-sort=\f[I]type\f[] Sort output according to a specific field. For \f[C]counters\f[] command the allowed values for this key are -\f[C]name\f[], \f[C]weight\f[], \f[C]frequency\f[] and \f[C]time\f[]. +\f[C]name\f[], \f[C]weight\f[], \f[C]frequency\f[] and \f[C]hits\f[]. Appending \f[C]:desc\f[] to any of these types inverts sorting order. .RS .RE @@ -268,7 +273,7 @@ rspamc\ uptime \f[] .fi .PP -Add custom rule\[aq]s weight: +Add custom rule's weight: .IP .nf \f[C] @@ -276,7 +281,7 @@ rspamc\ add_symbol\ test\ 1.5 \f[] .fi .PP -Add custom action\[aq]s weight: +Add custom action's weight: .IP .nf \f[C] @@ -285,5 +290,5 @@ rspamc\ add_action\ reject\ 7.1 .fi .SH SEE ALSO .PP -Rspamd documentation and source codes may be downloaded from +Rspamd documentation and source code may be downloaded from <https://rspamd.com/>. diff --git a/doc/rspamc.1.md b/doc/rspamc.1.md index f15ca186d..11e2f461c 100644 --- a/doc/rspamc.1.md +++ b/doc/rspamc.1.md @@ -114,7 +114,7 @@ requires input. : Add custom HTTP header for a request. You may specify header in format `name=value` or just `name` for an empty header. This option can be repeated multiple times. \--sort=*type* -: Sort output according to a specific field. For `counters` command the allowed values for this key are `name`, `weight`, `frequency` and `time`. Appending `:desc` to any of these types inverts sorting order. +: Sort output according to a specific field. For `counters` command the allowed values for this key are `name`, `weight`, `frequency` and `hits`. Appending `:desc` to any of these types inverts sorting order. \--commands : List available commands diff --git a/interface/index.html b/interface/index.html index 8cb21794f..1b3677646 100644 --- a/interface/index.html +++ b/interface/index.html @@ -390,6 +390,6 @@ </div> <div id="backDrop" class="modal-backdrop fade in" style="display:none"></div> -<script data-main="./js/main.js" src="./js/require.js"></script> +<script data-main="./js/main.js" src="./js/lib/require.min.js"></script> </body> </html> diff --git a/interface/js/app/config.js b/interface/js/app/config.js index d10fd6a60..19bc234ec 100644 --- a/interface/js/app/config.js +++ b/interface/js/app/config.js @@ -22,263 +22,251 @@ THE SOFTWARE. */ -define(['jquery'], -function($) { - var interface = {} +define(["jquery"], + function ($) { + "use strict"; + var ui = {}; - function save_map_success(rspamd) { - rspamd.alertMessage('alert-modal alert-success', 'Map data successfully saved'); - $('#modalDialog').modal('hide'); - } - function save_map_error(rspamd, serv, jqXHR, textStatus, errorThrown) { - rspamd.alertMessage('alert-modal alert-error', 'Save map error on ' + - serv.name + ': ' + errorThrown); - } - // @upload map from modal - function saveMap(rspamd, action, id) { - var data = $('#' + id).find('textarea').val(); - $.ajax({ - data: data, - dataType: 'text', - type: 'POST', - jsonp: false, - url: action, - beforeSend: function (xhr) { - xhr.setRequestHeader('Password', rspamd.getPassword()); - xhr.setRequestHeader('Map', id); - xhr.setRequestHeader('Debug', true); - }, - error: function (data) { - save_map_error(rspamd, 'local', null, null, data.statusText); - }, - success: function() {save_map_success(rspamd)}, - }); - } - - // @get maps id - function getMaps(rspamd) { - var items = []; - var $listmaps = $('#listMaps') - $listmaps.closest('.widget-box').hide(); - $.ajax({ - dataType: 'json', - url: 'maps', - jsonp: false, - beforeSend: function (xhr) { - xhr.setRequestHeader('Password', rspamd.getPassword()); - }, - error: function (data) { - rspamd.alertMessage('alert-modal alert-error', data.statusText); - }, - success: function (data) { - $listmaps.empty(); - $('#modalBody').empty(); - $tbody = $('<tbody>'); - - $.each(data, function (i, item) { - if ((item.editable === false || rspamd.read_only)) { - var label = '<span class="label label-default">Read</span>'; - } else { - var label = '<span class="label label-default">Read</span> <span class="label label-success">Write</span>'; - } - var $tr = $("<tr>"); - $('<td class="col-md-2 maps-cell">' + label + '</td>').appendTo($tr); - $span = $('<span class="map-link" data-toggle="modal" data-target="#modalDialog">' + item.uri + '</span>').data("item",item); - $span.wrap("<td>").parent().appendTo($tr); - $('<td>' + item.description + '</td>').appendTo($tr); - $tr.appendTo($tbody); - }); - $tbody.appendTo($listmaps); - $listmaps.closest('.widget-box').show(); - } - }); - } - // @get map by id - function getMapById(rspamd, item) { - return $.ajax({ - dataType: 'text', - url: 'getmap', - jsonp: false, - beforeSend: function (xhr) { - xhr.setRequestHeader('Password', rspamd.getPassword()); - xhr.setRequestHeader('Map', item.map); - }, - error: function () { - rspamd.alertMessage('alert-error', 'Cannot receive maps data'); - }, - success: function (text) { - var disabled = ''; - if ((item.editable === false || rspamd.read_only)) { - disabled = 'disabled="disabled"'; - } - - $("#"+item.map).remove(); - $('<form class="form-horizontal form-map" method="post" action="savemap" data-type="map" id="' + - item.map + '" style="display:none">' + - '<textarea class="list-textarea"' + disabled + '>' + text + - '</textarea>' + - '</form').appendTo('#modalBody'); - } - }); - } + function save_map_success(rspamd) { + rspamd.alertMessage("alert-modal alert-success", "Map data successfully saved"); + $("#modalDialog").modal("hide"); + } + function save_map_error(rspamd, serv, jqXHR, textStatus, errorThrown) { + var serv_name = (typeof serv === "string") ? serv : serv.name; + rspamd.alertMessage("alert-modal alert-error", "Save map error on " + + serv_name + ": " + errorThrown); + } + // @upload map from modal + function saveMap(rspamd, action, id) { + var data = $("#" + id).find("textarea").val(); + $.ajax({ + data: data, + dataType: "text", + type: "POST", + jsonp: false, + url: action, + beforeSend: function (xhr) { + xhr.setRequestHeader("Password", rspamd.getPassword()); + xhr.setRequestHeader("Map", id); + xhr.setRequestHeader("Debug", true); + }, + error: function (jqXHR) { + save_map_error(rspamd, "local", null, null, jqXHR.statusText); + }, + success: function () { save_map_success(rspamd); }, + }); + } - function loadActionsFromForm() { - var values = []; - var inputs = $('#actionsForm :input[data-id="action"]'); - // Rspamd order: [spam, rewrite_subject, probable_spam, greylist] - values[0] = parseFloat(inputs[3].value); - values[1] = parseFloat(inputs[2].value); - values[2] = parseFloat(inputs[1].value); - values[3] = parseFloat(inputs[0].value); + function loadActionsFromForm() { + var values = []; + var inputs = $("#actionsForm :input[data-id=\"action\"]"); + // Rspamd order: [spam, rewrite_subject, probable_spam, greylist] + values[0] = parseFloat(inputs[3].value); + values[1] = parseFloat(inputs[2].value); + values[2] = parseFloat(inputs[1].value); + values[3] = parseFloat(inputs[0].value); - return JSON.stringify(values); - } + return JSON.stringify(values); + } - function getActions(rspamd) { - $.ajax({ - dataType: 'json', - type: 'GET', - url: 'actions', - jsonp: false, - beforeSend: function (xhr) { - xhr.setRequestHeader('Password', rspamd.getPassword()); - }, - success: function (data) { - // Order of sliders greylist -> probable spam -> rewrite subject -> spam - $('#actionsBody').empty(); - $('#actionsForm').empty(); - var items = []; - $.each(data, function (i, item) { - var idx = -1; - var label; - if (item.action === 'add header') { - label = 'Probably Spam'; - idx = 1; - } else if (item.action === 'greylist') { - label = 'Greylist'; - idx = 0; - } else if (item.action === 'rewrite subject') { - label = 'Rewrite subject'; - idx = 2; - } else if (item.action === 'reject') { - label = 'Spam'; - idx = 3; - } - if (idx >= 0) { - items.push({ - idx: idx, - html: '<div class="form-group">' + - '<label class="control-label col-sm-2">' + label + '</label>' + - '<div class="controls slider-controls col-sm-10">' + - '<input class="action-scores form-control" data-id="action" type="number" value="' + item.value + '">' + - '</div>' + - '</div>' - }); - } - }); + ui.getActions = function getActions(rspamd, checked_server) { + rspamd.query("actions", { + success: function (data) { + $("#actionsBody").empty(); + $("#actionsForm").empty(); + var items = []; + $.each(data[0].data, function (i, item) { + var idx = -1; + var label; + if (item.action === "greylist") { + label = "Greylist"; + idx = 0; + } else if (item.action === "add header") { + label = "Probably Spam"; + idx = 1; + } else if (item.action === "rewrite subject") { + label = "Rewrite subject"; + idx = 2; + } else if (item.action === "reject") { + label = "Spam"; + idx = 3; + } + if (idx >= 0) { + items.push({ + idx: idx, + html: "<div class=\"form-group\">" + + "<label class=\"control-label col-sm-2\">" + label + "</label>" + + "<div class=\"controls slider-controls col-sm-10\">" + + "<input class=\"action-scores form-control\" data-id=\"action\" type=\"number\" value=\"" + item.value + "\">" + + "</div>" + + "</div>" + }); + } + }); - items.sort(function (a, b) { - return a.idx - b.idx; - }); + items.sort(function (a, b) { + return a.idx - b.idx; + }); - $('#actionsBody').html('<form id="actionsForm"><fieldset id="actionsFormField">' + + $("#actionsBody").html("<form id=\"actionsForm\"><fieldset id=\"actionsFormField\">" + items.map(function (e) { return e.html; - }).join('') + - '<br><div class="form-group">' + - '<div class="btn-group">' + - '<button class="btn btn-primary" type="button" id="saveActionsBtn">Save actions</button>' + - '<button class="btn btn-primary" type="button" id="saveActionsClusterBtn">Save cluster</button>' + - '</div></div></fieldset></form>'); - if (rspamd.read_only) { - $('#saveActionsClusterBtn').attr('disabled', true); - $('#saveActionsBtn').attr('disabled', true); - $('#actionsFormField').attr('disabled', true); - } + }).join("") + + "<br><div class=\"form-group\">" + + "<div class=\"btn-group\">" + + "<button class=\"btn btn-primary\" type=\"button\" id=\"saveActionsBtn\">Save actions</button>" + + "<button class=\"btn btn-primary\" type=\"button\" id=\"saveActionsClusterBtn\">Save cluster</button>" + + "</div></div></fieldset></form>"); + if (rspamd.read_only) { + $("#saveActionsClusterBtn").attr("disabled", true); + $("#saveActionsBtn").attr("disabled", true); + $("#actionsFormField").attr("disabled", true); + } - function saveActions(callback) { - var elts = loadActionsFromForm(); - // String to array for comparison - var eltsArray = JSON.parse(loadActionsFromForm()); - if (eltsArray[0] < 0) { - rspamd.alertMessage("alert-modal alert-error", "Spam can not be negative"); - } else if (eltsArray[1] < 0) { - rspamd.alertMessage("alert-modal alert-error", "Rewrite subject can not be negative"); - } else if (eltsArray[2] < 0) { - rspamd.alertMessage("alert-modal alert-error", "Probable spam can not be negative"); - } else if (eltsArray[3] < 0) { - rspamd.alertMessage("alert-modal alert-error", "Greylist can not be negative"); - } else if ( - (eltsArray[2] === null || eltsArray[3] < eltsArray[2]) && + function saveActions(server) { + var elts = loadActionsFromForm(); + // String to array for comparison + var eltsArray = JSON.parse(loadActionsFromForm()); + if (eltsArray[0] < 0) { + rspamd.alertMessage("alert-modal alert-error", "Spam can not be negative"); + } else if (eltsArray[1] < 0) { + rspamd.alertMessage("alert-modal alert-error", "Rewrite subject can not be negative"); + } else if (eltsArray[2] < 0) { + rspamd.alertMessage("alert-modal alert-error", "Probable spam can not be negative"); + } else if (eltsArray[3] < 0) { + rspamd.alertMessage("alert-modal alert-error", "Greylist can not be negative"); + } else if ( + (eltsArray[2] === null || eltsArray[3] < eltsArray[2]) && (eltsArray[1] === null || eltsArray[2] < eltsArray[1]) && (eltsArray[0] === null || eltsArray[1] < eltsArray[0]) - ) { - callback("saveactions", null, null, "POST", {}, { - data: elts, - dataType: "json" - }); - } else { - rspamd.alertMessage("alert-modal alert-error", "Incorrect order of metric actions threshold"); + ) { + rspamd.query("saveactions", { + method: "POST", + params: { + data: elts, + dataType: "json" + }, + server: server + }); + } else { + rspamd.alertMessage("alert-modal alert-error", "Incorrect order of metric actions threshold"); + } } - } - $('#saveActionsBtn').on('click', function() { - saveActions(rspamd.queryLocal); - }); - $('#saveActionsClusterBtn').on('click', function() { - saveActions(rspamd.queryNeighbours); - }); - }, - }); - } + $("#saveActionsBtn").on("click", function () { + saveActions(); + }); + $("#saveActionsClusterBtn").on("click", function () { + saveActions("All SERVERS"); + }); + }, + server: (checked_server === "All SERVERS") ? "local" : checked_server + }); + }; + + ui.getMaps = function (rspamd, checked_server) { + var $listmaps = $("#listMaps"); + $listmaps.closest(".widget-box").hide(); + rspamd.query("maps", { + success: function (json) { + var data = json[0].data; + $listmaps.empty(); + $("#modalBody").empty(); + var $tbody = $("<tbody>"); + + $.each(data, function (i, item) { + var label; + if ((item.editable === false || rspamd.read_only)) { + label = "<span class=\"label label-default\">Read</span>"; + } else { + label = "<span class=\"label label-default\">Read</span> <span class=\"label label-success\">Write</span>"; + } + var $tr = $("<tr>"); + $("<td class=\"col-md-2 maps-cell\">" + label + "</td>").appendTo($tr); + var $span = $("<span class=\"map-link\" data-toggle=\"modal\" data-target=\"#modalDialog\">" + item.uri + "</span>").data("item", item); + $span.wrap("<td>").parent().appendTo($tr); + $("<td>" + item.description + "</td>").appendTo($tr); + $tr.appendTo($tbody); + }); + $tbody.appendTo($listmaps); + $listmaps.closest(".widget-box").show(); + }, + server: (checked_server === "All SERVERS") ? "local" : checked_server + }); + }; - // @upload edited actions - interface.setup = function(rspamd) { + // @upload edited actions + ui.setup = function (rspamd, checked_server) { // Modal form for maps - $(document).on('click', '[data-toggle="modal"]', function () { - var item = $(this).data('item'); - getMapById(rspamd, item).done(function() { - $('#modalTitle').html(item.uri); - $('#' + item.map).first().show(); - $('#modalDialog .progress').hide(); - $('#modalDialog').modal(show = true, backdrop = true, keyboard = show); - if (item.editable === false) { - $('#modalSave').hide(); - $('#modalSaveAll').hide(); - } else { - $('#modalSave').show(); - $('#modalSaveAll').show(); - } + $(document).on("click", "[data-toggle=\"modal\"]", function () { + var item = $(this).data("item"); + rspamd.query("getmap", { + headers: { + Map: item.map + }, + success: function (data) { + var disabled = ""; + var text = data[0].data; + if ((item.editable === false || rspamd.read_only)) { + disabled = "disabled=\"disabled\""; + } + + $("#" + item.map).remove(); + $("<form id=\"" + item.map + "\" class=\"form-horizontal form-map\" style=\"display:none\"" + + " data-type=\"map\" action=\"savemap\" method=\"post\">" + + "<textarea class=\"list-textarea\"" + disabled + ">" + text + + "</textarea>" + + "</form>").appendTo("#modalBody"); + + $("#modalTitle").html(item.uri); + $("#" + item.map).first().show(); + $("#modalDialog .progress").hide(); + $("#modalDialog").modal({backdrop: true, keyboard: "show", show: true}); + if (item.editable === false) { + $("#modalSave").hide(); + $("#modalSaveAll").hide(); + } else { + $("#modalSave").show(); + $("#modalSaveAll").show(); + } + }, + errorMessage: "Cannot receive maps data", + server: (checked_server === "All SERVERS") ? "local" : checked_server + }); + return false; }); - return false; - }); - // close modal without saving - $('[data-dismiss="modal"]').on('click', function () { - $('#modalBody form').hide(); - }); - // @save forms from modal - $('#modalSave').on('click', function () { - var form = $('#modalBody').children().filter(':visible'); - var action = $(form).attr('action'); - var id = $(form).attr('id'); - saveMap(rspamd, action, id); - }); - $('#modalSaveAll').on('click', function () { - var form = $('#modalBody').children().filter(':visible'); - var action = $(form).attr('action'); - var id = $(form).attr('id'); - var data = $('#' + id).find('textarea').val(); - rspamd.queryNeighbours(action, save_map_success, save_map_error, "POST", { - "Map": id, - }, { - data: data, - dataType: "text", + // close modal without saving + $("[data-dismiss=\"modal\"]").on("click", function () { + $("#modalBody form").hide(); }); - }); - } - - interface.getActions = getActions; - interface.getMaps = getMaps; + // @save forms from modal + $("#modalSave").on("click", function () { + var form = $("#modalBody").children().filter(":visible"); + var action = $(form).attr("action"); + var id = $(form).attr("id"); + saveMap(rspamd, action, id); + }); + $("#modalSaveAll").on("click", function () { + var form = $("#modalBody").children().filter(":visible"); + var action = $(form).attr("action"); + var id = $(form).attr("id"); + var data = $("#" + id).find("textarea").val(); + rspamd.query(action, { + success: function () { + save_map_success(rspamd); + }, + errorMessage: "Save map error", + method: "POST", + headers: { + Map: id, + }, + params:{ + data: data, + dataType: "text", + } + }); + }); + }; - return interface; -}); + return ui; + }); diff --git a/interface/js/app/graph.js b/interface/js/app/graph.js index 9ae6a9297..46c6062fd 100644 --- a/interface/js/app/graph.js +++ b/interface/js/app/graph.js @@ -23,274 +23,247 @@ THE SOFTWARE. */ -define(['jquery', 'd3evolution', 'footable'], -function($, D3Evolution, unused) { - var rrd_pie_config = { - header: {}, - size: { - canvasWidth: 400, - canvasHeight: 180, - pieInnerRadius: "20%", - pieOuterRadius: "80%" - }, - labels: { - outer: { - format: "none" - }, - inner: { - hideWhenLessThanPercentage: 8 +/* global d3:false */ + +define(["jquery", "d3evolution", "footable"], + function ($, D3Evolution) { + "use strict"; + var rrd_pie_config = { + header: {}, + size: { + canvasWidth: 400, + canvasHeight: 180, + pieInnerRadius: "20%", + pieOuterRadius: "80%" }, - }, - misc: { - pieCenterOffset: { - x: -120, - y: 10, + labels: { + outer: { + format: "none" + }, + inner: { + hideWhenLessThanPercentage: 8 + }, }, - gradient: { - enabled: true, + misc: { + pieCenterOffset: { + x: -120, + y: 10, + }, + gradient: { + enabled: true, + }, }, - }, - }; + }; - var graph_options = { - title: "Rspamd throughput", - width: 1060, - height: 370, - yAxisLabel: "Message rate, msg/s", + var graph_options = { + title: "Rspamd throughput", + width: 1060, + height: 370, + yAxisLabel: "Message rate, msg/s", - legend: { - space: 140, - entries: [{ - label: "Rejected", - color: "#FF0000" - }, { - label: "Temporarily rejected", - color: "#BF8040" - }, { - label: "Subject rewritten", - color: "#FF6600" - }, { - label: "Probable spam", - color: "#FFAD00" - }, { - label: "Greylisted", - color: "#436EEE" - }, { - label: "Clean", - color: "#66CC00" - }] - } - }; + legend: { + space: 140, + entries: [{ + label: "Rejected", + color: "#FF0000" + }, { + label: "Temporarily rejected", + color: "#BF8040" + }, { + label: "Subject rewritten", + color: "#FF6600" + }, { + label: "Probable spam", + color: "#FFAD00" + }, { + label: "Greylisted", + color: "#436EEE" + }, { + label: "Clean", + color: "#66CC00" + }] + } + }; - // Get selectors' current state - function getSelector(id) { - var e = document.getElementById(id); - return e.options[e.selectedIndex].value; - } + // Get selectors' current state + function getSelector(id) { + var e = document.getElementById(id); + return e.options[e.selectedIndex].value; + } - function initGraph() { - var graph = new D3Evolution("graph", $.extend({}, graph_options, { - yScale: getSelector("selYScale"), - type: getSelector("selType"), - interpolate: getSelector("selInterpolate"), - convert: getSelector("selConvert"), - })); - $("#selYScale").change(function() { - graph.yScale(this.value); - }); - $("#selConvert").change(function () { - graph.convert(this.value); - }); - $("#selType").change(function () { - graph.type(this.value); - }); - $("#selInterpolate").change(function () { - graph.interpolate(this.value); - }); + function initGraph() { + var graph = new D3Evolution("graph", $.extend({}, graph_options, { + yScale: getSelector("selYScale"), + type: getSelector("selType"), + interpolate: getSelector("selInterpolate"), + convert: getSelector("selConvert"), + })); + $("#selYScale").change(function () { + graph.yScale(this.value); + }); + $("#selConvert").change(function () { + graph.convert(this.value); + }); + $("#selType").change(function () { + graph.type(this.value); + }); + $("#selInterpolate").change(function () { + graph.interpolate(this.value); + }); - return graph; - } + return graph; + } - function getRrdSummary(json, scaleFactor) { - var xExtents = d3.extent(d3.merge(json), function (d) { return d.x; }); - var timeInterval = xExtents[1] - xExtents[0]; + function getRrdSummary(json, scaleFactor) { + var xExtents = d3.extent(d3.merge(json), function (d) { return d.x; }); + var timeInterval = xExtents[1] - xExtents[0]; - return json.map(function (curr, i) { + return json.map(function (curr, i) { // Time intervals that don't have data are excluded from average calculation as d3.mean()ignores nulls - var avg = d3.mean(curr, function (d) { return d.y; }); - // To find an integral on the whole time interval we need to convert nulls to zeroes - var value = d3.mean(curr, function (d) { return +d.y; }) * timeInterval / scaleFactor; - var yExtents = d3.extent(curr, function (d) { return d.y; }); + var avg = d3.mean(curr, function (d) { return d.y; }); + // To find an integral on the whole time interval we need to convert nulls to zeroes + var value = d3.mean(curr, function (d) { return Number(d.y); }) * timeInterval / scaleFactor; + var yExtents = d3.extent(curr, function (d) { return d.y; }); - return { - label: graph_options.legend.entries[i].label, - value: value ^ 0, - min: +yExtents[0].toFixed(6), - avg: +avg.toFixed(6), - max: +yExtents[1].toFixed(6), - last: +curr[curr.length - 1].y.toFixed(6), - color: graph_options.legend.entries[i].color, - }; - }, []); - } + return { + label: graph_options.legend.entries[i].label, + value: value ^ 0, // eslint-disable-line no-bitwise + min: Number(yExtents[0].toFixed(6)), + avg: Number(avg.toFixed(6)), + max: Number(yExtents[1].toFixed(6)), + last: Number(curr[curr.length - 1].y.toFixed(6)), + color: graph_options.legend.entries[i].color, + }; + }, []); + } - function drawRrdTable(data, unit) { - var total_messages = 0; - var rows = data.map(function (curr, i) { - total_messages += curr.value; - return { - options: { - style: { - color: graph_options.legend.entries[i].color - } - }, - value: curr - }; - }, []); + function drawRrdTable(data, unit) { + var total_messages = 0; + var rows = data.map(function (curr, i) { + total_messages += curr.value; + return { + options: { + style: { + color: graph_options.legend.entries[i].color + } + }, + value: curr + }; + }, []); - document.getElementById('rrd-total-value').innerHTML = total_messages; + document.getElementById("rrd-total-value").innerHTML = total_messages; - $('#rrd-table').footable({ - sorting: { - enabled: true - }, - columns: [ - { name: "label", title: "Action" }, - { name: "value", title: "Messages", defaultContent: "" }, - { name: "min", title: "Minimum, " + unit, defaultContent: "" }, - { name: "avg", title: "Average, " + unit, defaultContent: "" }, - { name: "max", title: "Maximum, " + unit, defaultContent: "" }, - { name: "last", title: "Last, " + unit }, - ], - rows: rows - }); - } + $("#rrd-table").footable({ + sorting: { + enabled: true + }, + columns: [ + {name: "label", title: "Action"}, + {name: "value", title: "Messages", defaultContent: ""}, + {name: "min", title: "Minimum, " + unit, defaultContent: ""}, + {name: "avg", title: "Average, " + unit, defaultContent: ""}, + {name: "max", title: "Maximum, " + unit, defaultContent: ""}, + {name: "last", title: "Last, " + unit}, + ], + rows: rows + }); + } - var interface = {}; - var prevUnit = "msg/s"; + var ui = {}; + var prevUnit = "msg/s"; - interface.draw = function(rspamd, graphs, neighbours, checked_server, type) { + ui.draw = function (rspamd, graphs, neighbours, checked_server, type) { - function updateWidgets(data) { + function updateWidgets(data) { // Autoranging - var scaleFactor = 1; - var unit = "msg/s"; - const yMax = d3.max(d3.merge(data), function (d) { return d.y; }); - if (yMax < 1) { - scaleFactor = 60; - unit = "msg/min"; - data.forEach(function (s) { - s.forEach(function (d) { - if (d.y !== null) { d.y *= scaleFactor; } + var scaleFactor = 1; + var unit = "msg/s"; + var yMax = d3.max(d3.merge(data), function (d) { return d.y; }); + if (yMax < 1) { + scaleFactor = 60; + unit = "msg/min"; + data.forEach(function (s) { + s.forEach(function (d) { + if (d.y !== null) { d.y *= scaleFactor; } + }); }); - }); - } + } - graphs.graph.data(data); - if (unit != prevUnit) { - graphs.graph.yAxisLabel("Message rate, " + unit); - prevUnit = unit; - } + graphs.graph.data(data); + if (unit !== prevUnit) { + graphs.graph.yAxisLabel("Message rate, " + unit); + prevUnit = unit; + } - if (!data) { - graphs.rrd_pie.destroy(); - drawRrdTable([]); - return; + if (!data) { + graphs.rrd_pie.destroy(); + drawRrdTable([]); + return; + } + var rrd_summary = getRrdSummary(data, scaleFactor); + graphs.rrd_pie = rspamd.drawPie(graphs.rrd_pie, + "rrd-pie", + rrd_summary, + rrd_pie_config); + drawRrdTable(rrd_summary, unit); } - var rrd_summary = getRrdSummary(data, scaleFactor); - graphs.rrd_pie = rspamd.drawPie(graphs.rrd_pie, - "rrd-pie", - rrd_summary, - rrd_pie_config); - drawRrdTable(rrd_summary, unit); - } - if (graphs.graph === undefined) { - graphs.graph = initGraph(); - } + if (!graphs.graph) { + graphs.graph = initGraph(); + } - if (checked_server === "All SERVERS") { - rspamd.queryNeighbours("graph", function (req_data) { - var neighbours_data = req_data - .filter(function (d) { return d.status }) // filter out unavailable neighbours - .map(function (d){ return d.data; }); + rspamd.query("graph", { + success: function (req_data) { + var neighbours_data = req_data + .filter(function (d) { return d.status; }) // filter out unavailable neighbours + .map(function (d) { return d.data; }); - if (neighbours_data.length > 1) { - neighbours_data.reduce(function (res, curr) { - if ((curr[0][0].x !== res[0][0].x) || + if (neighbours_data.length > 1) { + neighbours_data.reduce(function (res, curr) { + if ((curr[0][0].x !== res[0][0].x) || (curr[0][curr[0].length - 1].x !== res[0][res[0].length - 1].x)) { - rspamd.alertMessage('alert-error', - 'Neighbours time extents do not match. Check if time is synchronized on all servers.'); - updateWidgets(); - return; - } + rspamd.alertMessage("alert-error", + "Neighbours time extents do not match. Check if time is synchronized on all servers."); + updateWidgets(); + return; + } - var data = []; - curr.forEach(function (action, j) { - data.push( - action.map(function (d, i) { - return { - x: d.x, - y: ((res[j][i].y === null) ? d.y : res[j][i].y + d.y) - }; - }) - ); + var data = []; + curr.forEach(function (action, j) { + data.push( + action.map(function (d, i) { + return { + x: d.x, + y: ((res[j][i].y === null) ? d.y : res[j][i].y + d.y) + }; + }) + ); + }); + updateWidgets(data); }); - updateWidgets(data); - }); - } - else { - updateWidgets(neighbours_data[0]); - } - }, - function (serv, jqXHR, textStatus, errorThrown) { - var alert_status = serv.name + '_alerted'; - - if (!(alert_status in sessionStorage)) { - sessionStorage.setItem(alert_status, true); - rspamd.alertMessage('alert-error', 'Cannot receive RRD data from: ' + - serv.name + ', error: ' + errorThrown); - } - }, "GET", {}, {}, { - type: type + } else { + updateWidgets(neighbours_data[0]); + } + }, + errorMessage: "Cannot receive throughput data", + errorOnceId: "alerted_graph_", + data: {type: type} }); - return; - } - - $.ajax({ - dataType: 'json', - type: 'GET', - url: neighbours[checked_server].url + 'graph', - jsonp: false, - data: { - "type": type - }, - beforeSend: function (xhr) { - xhr.setRequestHeader('Password', rspamd.getPassword()); - }, - success: function (data) { - updateWidgets(data); - }, - error: function (jqXHR, textStatus, errorThrown) { - rspamd.alertMessage('alert-error', 'Cannot receive throughput data: ' + - textStatus + ' ' + jqXHR.status + ' ' + errorThrown); - } - }); - }; + }; - interface.setup = function() { + ui.setup = function () { // Handling mouse events on overlapping elements - $("#rrd-pie").mouseover(function () { - $("#rrd-pie").css("z-index", "200"); - $("#rrd-table_toggle").css("z-index", "300"); - }); - $("#rrd-table_toggle").mouseover(function () { - $("#rrd-pie").css("z-index", "0"); - $("#rrd-table_toggle").css("z-index", "0"); - }); + $("#rrd-pie").mouseover(function () { + $("#rrd-pie").css("z-index", "200"); + $("#rrd-table_toggle").css("z-index", "300"); + }); + $("#rrd-table_toggle").mouseover(function () { + $("#rrd-pie").css("z-index", "0"); + $("#rrd-table_toggle").css("z-index", "0"); + }); - return getSelector("selData"); - }; + return getSelector("selData"); + }; - return interface; -}); + return ui; + }); diff --git a/interface/js/app/history.js b/interface/js/app/history.js index c5cd0d37c..6fe11a5f5 100644 --- a/interface/js/app/history.js +++ b/interface/js/app/history.js @@ -22,54 +22,57 @@ THE SOFTWARE. */ -define(['jquery', 'footable', 'humanize'], -function($, _, Humanize) { - var interface = {}; - var ft = {}; - var htmlEscapes = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''', - '/': '/', - '`': '`', - '=': '=' - }; - var htmlEscaper = /[&<>"'\/`=]/g; - var symbolDescriptions = {}; - - EscapeHTML = function(string) { - return ('' + string).replace(htmlEscaper, function(match) { - return htmlEscapes[match]; - }); - }; - - escape_HTML_array = function (arr) { - arr.forEach(function (d, i) { arr[i] = EscapeHTML(d) }); - }; - - function unix_time_format(tm) { - var date = new Date(tm ? tm * 1000 : 0); - return date.toLocaleString(); - } - - function preprocess_item(item) { - for (var prop in item) { - switch (prop) { +/* global FooTable:false */ + +define(["jquery", "footable", "humanize"], + function ($, _, Humanize) { + "use strict"; + var ui = {}; + var ft = {}; + var htmlEscapes = { + "&": "&", + "<": "<", + ">": ">", + "\"": """, + "'": "'", + "/": "/", + "`": "`", + "=": "=" + }; + var htmlEscaper = /[&<>"'/`=]/g; + var symbolDescriptions = {}; + + var EscapeHTML = function (string) { + return (String(string)).replace(htmlEscaper, function (match) { + return htmlEscapes[match]; + }); + }; + + var escape_HTML_array = function (arr) { + arr.forEach(function (d, i) { arr[i] = EscapeHTML(d); }); + }; + + function unix_time_format(tm) { + var date = new Date(tm ? tm * 1000 : 0); + return date.toLocaleString(); + } + + function preprocess_item(item) { + for (var prop in item) { + switch (prop) { case "rcpt_mime": case "rcpt_smtp": escape_HTML_array(item[prop]); break; case "symbols": - Object.keys(item.symbols).map(function(key) { + Object.keys(item.symbols).map(function (key) { var sym = item.symbols[key]; if (!sym.name) { sym.name = key; } - sym.name = EscapeHTML(key); + sym.name = EscapeHTML(sym.name); if (sym.description) { - sym.description = EscapeHTML(sym.description); + sym.description = EscapeHTML(sym.description); } if (sym.options) { @@ -78,143 +81,185 @@ function($, _, Humanize) { }); break; default: - if (typeof (item[prop]) == "string") { + if (typeof (item[prop]) === "string") { item[prop] = EscapeHTML(item[prop]); } + } } - } - if (item.action === 'clean' || item.action === 'no action') { - item.action = "<div style='font-size:11px' class='label label-success'>" + item.action + "</div>"; - } else if (item.action === 'rewrite subject' || item.action === 'add header' || item.action === 'probable spam') { - item.action = "<div style='font-size:11px' class='label label-warning'>" + item.action + "</div>"; - } else if (item.action === 'spam' || item.action === 'reject') { - item.action = "<div style='font-size:11px' class='label label-danger'>" + item.action + "</div>"; - } else { - item.action = "<div style='font-size:11px' class='label label-info'>" + item.action + "</div>"; - } - - var score_content; - if (item.score < item.required_score) { - score_content = "<span class='text-success'>" + item.score.toFixed(2) + " / " + item.required_score + "</span>"; - } else { - score_content = "<span class='text-danger'>" + item.score.toFixed(2) + " / " + item.required_score + "</span>"; - } + if (item.action === "clean" || item.action === "no action") { + item.action = "<div style='font-size:11px' class='label label-success'>" + item.action + "</div>"; + } else if (item.action === "rewrite subject" || item.action === "add header" || item.action === "probable spam") { + item.action = "<div style='font-size:11px' class='label label-warning'>" + item.action + "</div>"; + } else if (item.action === "spam" || item.action === "reject") { + item.action = "<div style='font-size:11px' class='label label-danger'>" + item.action + "</div>"; + } else { + item.action = "<div style='font-size:11px' class='label label-info'>" + item.action + "</div>"; + } - item.score = { - "options": { - "sortValue": item.score - }, - "value": score_content - }; + var score_content; + if (item.score < item.required_score) { + score_content = "<span class='text-success'>" + item.score.toFixed(2) + " / " + item.required_score + "</span>"; + } else { + score_content = "<span class='text-danger'>" + item.score.toFixed(2) + " / " + item.required_score + "</span>"; + } - if (item.user == null) { - item.user = "none"; + item.score = { + options: { + sortValue: item.score + }, + value: score_content + }; } - } - function process_history_v2(data) { - var items = []; + function process_history_v2(data) { + // Display no more than rcpt_lim recipients + var rcpt_lim = 3; + var items = []; - function getSelector(id) { - var e = document.getElementById(id); - return e.options[e.selectedIndex].value; - } - var compare = (getSelector("selSymOrder") === "score") - ? function (e1, e2) { - return Math.abs(e1.score) < Math.abs(e2.score); + function getSelector(id) { + var e = document.getElementById(id); + return e.options[e.selectedIndex].value; } - : function (e1, e2) { - return e1.name.localeCompare(e2.name); - }; + var compare = (getSelector("selSymOrder") === "score") + ? function (e1, e2) { + return Math.abs(e2.score) - Math.abs(e1.score); + } + : function (e1, e2) { + return e1.name.localeCompare(e2.name); + }; - $.each(data.rows, - function (i, item) { + $("#selSymOrder, label[for='selSymOrder']").show(); - preprocess_item(item); - Object.keys(item.symbols).map(function(key) { - var sym = item.symbols[key]; + $.each(data.rows, + function (i, item) { + function more(p) { + var l = item[p].length; + return (l > rcpt_lim) ? " … (" + l + ")" : ""; + } + function format_rcpt(smtp, mime) { + var full = ""; + var shrt = ""; + if (smtp) { + full = "[" + item.rcpt_smtp.join(", ") + "] "; + shrt = "[" + item.rcpt_smtp.slice(0, rcpt_lim).join(",​") + more("rcpt_smtp") + "]"; + if (mime) { + full += " "; + shrt += " "; + } + } + if (mime) { + full += item.rcpt_mime.join(", "); + shrt += item.rcpt_mime.slice(0, rcpt_lim).join(",​") + more("rcpt_mime"); + } + return {full: full, shrt: shrt}; + } - if (sym.description) { - var str = '<strong><abbr data-sym-key="' + key + '">' + sym.name + '</abbr></strong>' + "(" + sym.score + ")"; + preprocess_item(item); + Object.keys(item.symbols).map(function (key) { + var str; + var sym = item.symbols[key]; - // Store description for tooltip - symbolDescriptions[key] = sym.description; - } else { - var str = '<strong>' + sym.name + '</strong>' + "(" + sym.score + ")"; - } + if (sym.description) { + str = "<strong><abbr data-sym-key=\"" + key + "\">" + sym.name + "</abbr></strong>(" + sym.score + ")"; - if (sym.options) { - str += '[' + sym.options.join(",") + "]"; - } - item.symbols[key].str = str; - }); - item.symbols = Object.keys(item.symbols). - map(function(key) { - return item.symbols[key]; - }). - sort(compare). - map(function(e) { return e.str; }). - join("<br>\n"); - item.time = { - "value": unix_time_format(item.unix_time), - "options": { - "sortValue": item.unix_time - } - }; - var scan_time = item.time_real.toFixed(3) + ' / ' + + // Store description for tooltip + symbolDescriptions[key] = sym.description; + } else { + str = "<strong>" + sym.name + "</strong>(" + sym.score + ")"; + } + + if (sym.options) { + str += "[" + sym.options.join(",") + "]"; + } + item.symbols[key].str = str; + }); + item.symbols = Object.keys(item.symbols) + .map(function (key) { + return item.symbols[key]; + }) + .sort(compare) + .map(function (e) { return e.str; }) + .join("<br>\n"); + item.time = { + value: unix_time_format(item.unix_time), + options: { + sortValue: item.unix_time + } + }; + var scan_time = item.time_real.toFixed(3) + " / " + item.time_virtual.toFixed(3); - item.scan_time = { - "options": { - "sortValue": item.time_real - }, - "value": scan_time - }; - item.id = item['message-id']; - if ($(item.rcpt_mime).not(item.rcpt_smtp).length !== 0 || $(item.rcpt_smtp).not(item.rcpt_mime).length !== 0) { - item.rcpt_mime = "[" + item.rcpt_smtp.join(",​") + "] " + item.rcpt_mime.join(",​"); - } else { - item.rcpt_mime = item.rcpt_mime.join(",​"); - } - if (item.sender_mime !== item.sender_smtp) { - item.sender_mime = "[" + item.sender_smtp + "] " + item.sender_mime; - } - items.push(item); - }); + item.scan_time = { + options: { + sortValue: item.time_real + }, + value: scan_time + }; + item.id = item["message-id"]; + + var rcpt = {}; + if (!item.rcpt_mime.length) { + rcpt = format_rcpt(true, false); + } else if ($(item.rcpt_mime).not(item.rcpt_smtp).length !== 0 || $(item.rcpt_smtp).not(item.rcpt_mime).length !== 0) { + rcpt = format_rcpt(true, true); + } else { + rcpt = format_rcpt(false, true); + } + item.rcpt_mime_short = rcpt.shrt; + item.rcpt_mime = rcpt.full; - return items; - } + if (item.sender_mime !== item.sender_smtp) { + item.sender_mime = "[" + item.sender_smtp + "] " + item.sender_mime; + } + items.push(item); + }); - function process_history_legacy(data) { - var items = []; + return items; + } - $.each(data, function (i, item) { - item.time = unix_time_format(item.unix_time); - preprocess_item(item); - item.scan_time = { - "options": { - "sortValue": item.scan_time - }, - "value": item.scan_time - }; - item.time = { - "value": unix_time_format(item.unix_time), - "options": { - "sortValue": item.unix_time - } + function process_history_legacy(data) { + var items = []; + + var compare = function (e1, e2) { + return e1.name.localeCompare(e2.name); }; - items.push(item) - }); + $("#selSymOrder, label[for='selSymOrder']").hide(); - return items; - } + $.each(data, function (i, item) { + item.time = unix_time_format(item.unix_time); + preprocess_item(item); + item.scan_time = { + options: { + sortValue: item.scan_time + }, + value: item.scan_time + }; + item.symbols = Object.keys(item.symbols) + .map(function (key) { + return item.symbols[key]; + }) + .sort(compare) + .map(function (e) { return e.name; }) + .join(", "); + item.time = { + value: unix_time_format(item.unix_time), + options: { + sortValue: item.unix_time + } + }; - function columns_v2() { - return [{ - "name": "id", - "title": "ID", - "style": { + items.push(item); + }); + + return items; + } + + function columns_v2() { + return [{ + name: "id", + title: "ID", + style: { "font-size": "11px", "minWidth": 130, "overflow": "hidden", @@ -223,111 +268,119 @@ function($, _, Humanize) { "whiteSpace": "normal" } }, { - "name": "ip", - "title": "IP address", - "breakpoints": "xs sm md", - "style": { + name: "ip", + title: "IP address", + breakpoints: "xs sm md", + style: { "font-size": "11px", "minWidth": 88 } }, { - "name": "sender_mime", - "title": "[Envelope From] From", - "breakpoints": "xs sm md", - "style": { + name: "sender_mime", + title: "[Envelope From] From", + breakpoints: "xs sm md", + style: { "font-size": "11px", "minWidth": 100, "maxWidth": 200, "word-wrap": "break-word" } }, { - "name": "rcpt_mime", - "title": "[Envelope To] To/Cc/Bcc", - "breakpoints": "xs sm md", - "style": { + name: "rcpt_mime_short", + title: "[Envelope To] To/Cc/Bcc", + breakpoints: "xs sm md", + style: { "font-size": "11px", "minWidth": 100, "maxWidth": 200, "word-wrap": "break-word" } }, { - "name": "subject", - "title": "Subject", - "breakpoints": "xs sm md", - "style": { + name: "rcpt_mime", + title: "[Envelope To] To/Cc/Bcc", + breakpoints: "all", + style: { + "font-size": "11px", + "word-wrap": "break-word" + } + }, { + name: "subject", + title: "Subject", + breakpoints: "xs sm md", + style: { "font-size": "11px", "word-break": "break-all", "minWidth": 150 } }, { - "name": "action", - "title": "Action", - "style": { + name: "action", + title: "Action", + style: { "font-size": "11px", "minwidth": 82 } }, { - "name": "score", - "title": "Score", - "style": { + name: "score", + title: "Score", + style: { "font-size": "11px", "maxWidth": 110 }, - "sortValue": function(val) { return Number(val.options.sortValue); } + sortValue: function (val) { return Number(val.options.sortValue); } }, { - "name": "symbols", - "title": "Symbols", - "breakpoints": "all", - "style": { + name: "symbols", + title: "Symbols", + breakpoints: "all", + style: { "font-size": "11px", "width": 550, "maxWidth": 550 } }, { - "name": "size", - "title": "Msg size", - "breakpoints": "xs sm md", - "style": { + name: "size", + title: "Msg size", + breakpoints: "xs sm md", + style: { "font-size": "11px", "minwidth": 50, }, - "formatter": Humanize.compactInteger + formatter: Humanize.compactInteger }, { - "name": "scan_time", - "title": "Scan time", - "breakpoints": "xs sm md", - "style": { + name: "scan_time", + title: "Scan time", + breakpoints: "xs sm md", + style: { "font-size": "11px", "maxWidth": 72 }, - "sortValue": function(val) { return Number(val.options.sortValue); } + sortValue: function (val) { return Number(val.options.sortValue); } }, { - "sorted": true, - "direction": "DESC", - "name": "time", - "title": "Time", - "style": { + sorted: true, + direction: "DESC", + name: "time", + title: "Time", + style: { "font-size": "11px" }, - "sortValue": function(val) { return Number(val.options.sortValue); } + sortValue: function (val) { return Number(val.options.sortValue); } }, { - "name": "user", - "title": "Authenticated user", - "breakpoints": "xs sm md", - "style": { + name: "user", + title: "Authenticated user", + breakpoints: "xs sm md", + style: { "font-size": "11px", "minWidth": 100, "maxWidth": 130, "word-wrap": "break-word" } }]; - } + } - function columns_legacy() { - return [{ - "name": "id", - "title": "ID", - "style": { + function columns_legacy() { + return [{ + name: "id", + title: "ID", + style: { "font-size": "11px", "width": 300, "maxWidth": 300, @@ -337,414 +390,338 @@ function($, _, Humanize) { "whiteSpace": "nowrap" } }, { - "name": "ip", - "title": "IP address", - "breakpoints": "xs sm", - "style": { + name: "ip", + title: "IP address", + breakpoints: "xs sm", + style: { "font-size": "11px", "width": 150, "maxWidth": 150 } }, { - "name": "action", - "title": "Action", - "style": { + name: "action", + title: "Action", + style: { "font-size": "11px", "width": 110, "maxWidth": 110 } }, { - "name": "score", - "title": "Score", - "style": { + name: "score", + title: "Score", + style: { "font-size": "11px", "maxWidth": 110 }, - "sortValue": function(val) { return Number(val.options.sortValue); } + sortValue: function (val) { return Number(val.options.sortValue); } }, { - "name": "symbols", - "title": "Symbols", - "breakpoints": "all", - "style": { + name: "symbols", + title: "Symbols", + breakpoints: "all", + style: { "font-size": "11px", "width": 550, "maxWidth": 550 } }, { - "name": "size", - "title": "Message size", - "breakpoints": "xs sm", - "style": { + name: "size", + title: "Message size", + breakpoints: "xs sm", + style: { "font-size": "11px", "width": 120, "maxWidth": 120 }, - "formatter": Humanize.compactInteger + formatter: Humanize.compactInteger }, { - "name": "scan_time", - "title": "Scan time", - "breakpoints": "xs sm", - "style": { + name: "scan_time", + title: "Scan time", + breakpoints: "xs sm", + style: { "font-size": "11px", "maxWidth": 80 }, - "sortValue": function(val) { return Number(val.options.sortValue); } + sortValue: function (val) { return Number(val.options.sortValue); } }, { - "sorted": true, - "direction": "DESC", - "name": "time", - "title": "Time", - "style": { + sorted: true, + direction: "DESC", + name: "time", + title: "Time", + style: { "font-size": "11px" }, - "sortValue": function(val) { return Number(val.options.sortValue); } + sortValue: function (val) { return Number(val.options.sortValue); } }, { - "name": "user", - "title": "Authenticated user", - "breakpoints": "xs sm", - "style": { + name: "user", + title: "Authenticated user", + breakpoints: "xs sm", + style: { "font-size": "11px", "width": 200, "maxWidth": 200 } }]; - } - - var process_functions = { - "2": process_history_v2, - "legacy": process_history_legacy - }; - - var columns = { - "2": columns_v2, - "legacy": columns_legacy - }; - - function process_history_data(data) { - var pf = process_functions.legacy; - - if (data.version) { - var strkey = data.version.toString(); - if (process_functions[strkey]) { - pf = process_functions[strkey]; - } } - return pf(data); - } + var process_functions = { + 2: process_history_v2, + legacy: process_history_legacy + }; - function get_history_columns(data) { - var func = columns.legacy; + var columns = { + 2: columns_v2, + legacy: columns_legacy + }; + + function process_history_data(data) { + var pf = process_functions.legacy; - if (data.version) { - var strkey = data.version.toString(); - if (columns[strkey]) { - func = columns[strkey]; + if (data.version) { + var strkey = data.version.toString(); + if (process_functions[strkey]) { + pf = process_functions[strkey]; + } } + + return pf(data); } - return func(); - } - - interface.getHistory = function (rspamd, tables, neighbours, checked_server) { - FooTable.actionFilter = FooTable.Filtering.extend({ - construct : function(instance) { - this._super(instance); - this.actions = [ 'reject', 'add header', 'greylist', - 'no action', 'soft reject', 'rewrite subject' ]; - this.def = 'Any action'; - this.$action = null; - }, - $create : function() { - this._super(); - var self = this, $form_grp = $('<div/>', { - 'class' : 'form-group' - }).append($('<label/>', { - 'class' : 'sr-only', - text : 'Action' - })).prependTo(self.$form); - - self.$action = $('<select/>', { - 'class' : 'form-control' - }).on('change', { - self : self - }, self._onStatusDropdownChanged).append( - $('<option/>', { - text : self.def - })).appendTo($form_grp); - - $.each(self.actions, function(i, action) { - self.$action.append($('<option/>').text(action)); - }); - }, - _onStatusDropdownChanged : function(e) { - var self = e.data.self, selected = $(this).val(); - if (selected !== self.def) { - if(selected === "reject"){ - self.addFilter('action', 'reject -soft', [ 'action' ]); - } else { - self.addFilter('action', selected, [ 'action' ]); - } - } else { - self.removeFilter('action'); - } - self.filter(); - }, - draw : function() { - this._super(); - var action = this.find('action'); - if (action instanceof FooTable.Filter) { - if(action.query.val() === 'reject -soft'){ - this.$action.val('reject'); - } else { - this.$action.val(action.query.val()); + function get_history_columns(data) { + var func = columns.legacy; + + if (data.version) { + var strkey = data.version.toString(); + if (columns[strkey]) { + func = columns[strkey]; } - } else { - this.$action.val(this.def); } - } - }); - var drawTooltips = function() { - // Update symbol description tooltips - $.each(symbolDescriptions, function (key, description) { - $('abbr[data-sym-key=' + key + ']').tooltip({ - "placement": "bottom", - "html": true, - "title": description - }); - }); + return func(); } - if (checked_server === "All SERVERS") { - rspamd.queryNeighbours("history", function (req_data) { - function differentVersions() { - const dv = neighbours_data.some(function (e) { - return e.version !== neighbours_data[0].version; + ui.getHistory = function (rspamd, tables, neighbours, checked_server) { + FooTable.actionFilter = FooTable.Filtering.extend({ + construct : function (instance) { + this._super(instance); + this.actions = ["reject", "add header", "greylist", + "no action", "soft reject", "rewrite subject"]; + this.def = "Any action"; + this.$action = null; + }, + $create : function () { + this._super(); + var self = this, $form_grp = $("<div/>", { + class : "form-group" + }).append($("<label/>", { + class : "sr-only", + text : "Action" + })).prependTo(self.$form); + + self.$action = $("<select/>", { + class : "form-control" + }).on("change", { + self : self + }, self._onStatusDropdownChanged).append( + $("<option/>", { + text : self.def + })).appendTo($form_grp); + + $.each(self.actions, function (i, action) { + self.$action.append($("<option/>").text(action)); }); - if (dv) { - rspamd.alertMessage('alert-error', - 'Neighbours history backend versions do not match. Cannot display history.'); - return true; + }, + _onStatusDropdownChanged : function (e) { + var self = e.data.self, selected = $(this).val(); + if (selected !== self.def) { + if (selected === "reject") { + self.addFilter("action", "reject -soft", ["action"]); + } else { + self.addFilter("action", selected, ["action"]); + } + } else { + self.removeFilter("action"); + } + self.filter(); + }, + draw : function () { + this._super(); + var action = this.find("action"); + if (action instanceof FooTable.Filter) { + if (action.query.val() === "reject -soft") { + this.$action.val("reject"); + } else { + this.$action.val(action.query.val()); + } + } else { + this.$action.val(this.def); } } + }); - var neighbours_data = req_data - .filter(function (d) { return d.status }) // filter out unavailable neighbours - .map(function (d){ return d.data; }); - if (neighbours_data.length && !differentVersions()) { - var data = {}; - if (neighbours_data[0].version) { - data.rows = [].concat.apply([], neighbours_data - .map(function (e) { - return e.rows; - })); - data.version = neighbours_data[0].version; - } - else { - // Legacy version - data = [].concat.apply([], neighbours_data); + var drawTooltips = function () { + // Update symbol description tooltips + $.each(symbolDescriptions, function (key, description) { + $("abbr[data-sym-key=" + key + "]").tooltip({ + placement: "bottom", + html: true, + title: description + }); + }); + }; + + rspamd.query("history", { + success: function (req_data) { + function differentVersions(neighbours_data) { + var dv = neighbours_data.some(function (e) { + return e.version !== neighbours_data[0].version; + }); + if (dv) { + rspamd.alertMessage("alert-error", + "Neighbours history backend versions do not match. Cannot display history."); + return true; + } } - var items = process_history_data(data); - ft.history = FooTable.init("#historyTable", { - "columns": get_history_columns(data), - "rows": items, - "paging": { - "enabled": true, - "limit": 5, - "size": 25 - }, - "filtering": { - "enabled": true, - "position": "left", - "connectors": false - }, - "sorting": { - "enabled": true - }, - "components": { - "filtering": FooTable.actionFilter - }, - "on": { - "ready.ft.table": drawTooltips, - "after.ft.sorting": drawTooltips, - "after.ft.paging": drawTooltips, - "after.ft.filtering": drawTooltips + var neighbours_data = req_data + .filter(function (d) { return d.status; }) // filter out unavailable neighbours + .map(function (d) { return d.data; }); + if (neighbours_data.length && !differentVersions(neighbours_data)) { + var data = {}; + if (neighbours_data[0].version) { + data.rows = [].concat.apply([], neighbours_data + .map(function (e) { + return e.rows; + })); + data.version = neighbours_data[0].version; + } else { + // Legacy version + data = [].concat.apply([], neighbours_data); } - }); - } else { - if (ft.history) { + var items = process_history_data(data); + ft.history = FooTable.init("#historyTable", { + columns: get_history_columns(data), + rows: items, + paging: { + enabled: true, + limit: 5, + size: 25 + }, + filtering: { + enabled: true, + position: "left", + connectors: false + }, + sorting: { + enabled: true + }, + components: { + filtering: FooTable.actionFilter + }, + on: { + "ready.ft.table": drawTooltips, + "after.ft.sorting": drawTooltips, + "after.ft.paging": drawTooltips, + "after.ft.filtering": drawTooltips + } + }); + } else if (ft.history) { ft.history.destroy(); - ft.history = undefined; + delete ft.history; } - } - }); - } - else { - $.ajax({ - dataType: 'json', - url: neighbours[checked_server].url + 'history', - jsonp: false, - beforeSend: function (xhr) { - xhr.setRequestHeader('Password', rspamd.getPassword()); - }, - error: function () { - rspamd.alertMessage('alert-error', 'Cannot receive history'); }, - success: function (data) { - var items = process_history_data(data); - ft.history = FooTable.init("#historyTable", { - "columns": get_history_columns(data), - "rows": items, - "paging": { - "enabled": true, - "limit": 5, - "size": 25 - }, - "filtering": { - "enabled": true, - "position": "left", - "connectors": false - }, - "sorting": { - "enabled": true - }, - "components": { - "filtering": FooTable.actionFilter - }, - "on": { - "ready.ft.table": drawTooltips, - "after.ft.sorting": drawTooltips, - "after.ft.paging": drawTooltips, - "after.ft.filtering": drawTooltips - } - }); - } + errorMessage: "Cannot receive history", }); - } - $('#updateHistory').off('click'); - $('#updateHistory').on('click', function (e) { - e.preventDefault(); - interface.getHistory(rspamd, tables, neighbours, checked_server); - }); - $("#selSymOrder").unbind().change(function() { - interface.getHistory(rspamd, tables, neighbours, checked_server); - }); - - // @reset history log - $('#resetHistory').off('click'); - $('#resetHistory').on('click', function (e) { - e.preventDefault(); - if (!confirm("Are you sure you want to reset history log?")) { - return; - } - if (ft.history) { - ft.history.destroy(); - ft.history = undefined; - } - if (ft.errors) { - ft.errors.destroy(); - ft.errors = undefined; - } - if (checked_server === "All SERVERS") { - rspamd.queryNeighbours("errors", function (data) { - interface.getHistory(rspamd, tables, neighbours, checked_server); - interface.getErrors(rspamd, tables, neighbours, checked_server); - }); - } - else { - $.ajax({ - dataType: 'json', - type: 'GET', - jsonp: false, - url: neighbours[checked_server].url + 'historyreset', - beforeSend: function (xhr) { - xhr.setRequestHeader('Password', rspamd.getPassword()); - }, + + $("#updateHistory").off("click"); + $("#updateHistory").on("click", function (e) { + e.preventDefault(); + ui.getHistory(rspamd, tables, neighbours, checked_server); + }); + $("#selSymOrder").unbind().change(function () { + ui.getHistory(rspamd, tables, neighbours, checked_server); + }); + + // @reset history log + $("#resetHistory").off("click"); + $("#resetHistory").on("click", function (e) { + e.preventDefault(); + if (!confirm("Are you sure you want to reset history log?")) { // eslint-disable-line no-alert + return; + } + if (ft.history) { + ft.history.destroy(); + delete ft.history; + } + if (ft.errors) { + ft.errors.destroy(); + delete ft.errors; + } + + rspamd.query("historyreset", { success: function () { - interface.getHistory(rspamd, tables, neighbours, checked_server); - interface.getErrors(rspamd, tables, neighbours, checked_server); + ui.getHistory(rspamd, tables, neighbours, checked_server); + ui.getErrors(rspamd, tables, neighbours, checked_server); }, - error: function (data) { - rspamd.alertMessage('alert-modal alert-error', data.statusText); - } + errorMessage: "Cannot reset history log" }); - } - }); - }; - - function drawErrorsTable(data) { - var items = []; - $.each(data, function (i, item) { - items.push( - item.ts = unix_time_format(item.ts) - ); - }); - ft.errors = FooTable.init("#errorsLog", { - "columns": [ - {"sorted": true,"direction": "DESC","name":"ts","title":"Time","style":{"font-size":"11px","width":300,"maxWidth":300}}, - {"name":"type","title":"Worker type","breakpoints":"xs sm","style":{"font-size":"11px","width":150,"maxWidth":150}}, - {"name":"pid","title":"PID","breakpoints":"xs sm","style":{"font-size":"11px","width":110,"maxWidth":110}}, - {"name":"module","title":"Module","style":{"font-size":"11px"}}, - {"name":"id","title":"Internal ID","style":{"font-size":"11px"}}, - {"name":"message","title":"Message","breakpoints":"xs sm","style":{"font-size":"11px"}}, - ], - "rows": data, - "paging": { - "enabled": true, - "limit": 5, - "size": 25 - }, - "filtering": { - "enabled": true, - "position": "left", - "connectors": false - }, - "sorting": { - "enabled": true - } - }); - } - - interface.getErrors = function(rspamd, tables, neighbours, checked_server) { - if (rspamd.read_only) return; - - if (checked_server !== "All SERVERS") { - $.ajax({ - dataType: 'json', - url: neighbours[checked_server].url + 'errors', - jsonp: false, - beforeSend: function (xhr) { - xhr.setRequestHeader('Password', rspamd.getPassword()); + }); + }; + + function drawErrorsTable(data) { + var items = []; + $.each(data, function (i, item) { + items.push( + item.ts = unix_time_format(item.ts) + ); + }); + ft.errors = FooTable.init("#errorsLog", { + columns: [ + {sorted: true, direction: "DESC", name:"ts", title:"Time", style:{"font-size":"11px", "width":300, "maxWidth":300}}, + {name:"type", title:"Worker type", breakpoints:"xs sm", style:{"font-size":"11px", "width":150, "maxWidth":150}}, + {name:"pid", title:"PID", breakpoints:"xs sm", style:{"font-size":"11px", "width":110, "maxWidth":110}}, + {name:"module", title:"Module", style:{"font-size":"11px"}}, + {name:"id", title:"Internal ID", style:{"font-size":"11px"}}, + {name:"message", title:"Message", breakpoints:"xs sm", style:{"font-size":"11px"}}, + ], + rows: data, + paging: { + enabled: true, + limit: 5, + size: 25 }, - error: function () { - rspamd.alertMessage('alert-error', 'Cannot receive errors'); + filtering: { + enabled: true, + position: "left", + connectors: false }, - success: function (data) { - drawErrorsTable(data); + sorting: { + enabled: true } }); - } else { - rspamd.queryNeighbours("errors", function (req_data) { - var neighbours_data = req_data - .filter(function (d) { - return d.status - }) // filter out unavailable neighbours - .map(function (d) { - return d.data; - }); - drawErrorsTable([].concat.apply([], neighbours_data)); - }); } - $('#updateErrors').off('click'); - $('#updateErrors').on('click', function (e) { - e.preventDefault(); - interface.getErrors(rspamd, tables, neighbours, checked_server); - }); - }; - - interface.setup = function(rspamd, tables) { - }; - return interface; -}); + + ui.getErrors = function (rspamd, tables, neighbours, checked_server) { + if (rspamd.read_only) return; + + rspamd.query("errors", { + success: function (req_data) { + var neighbours_data = req_data + .filter(function (d) { + return d.status; + }) // filter out unavailable neighbours + .map(function (d) { + return d.data; + }); + drawErrorsTable([].concat.apply([], neighbours_data)); + } + }); + + $("#updateErrors").off("click"); + $("#updateErrors").on("click", function (e) { + e.preventDefault(); + ui.getErrors(rspamd, tables, neighbours, checked_server); + }); + }; + + return ui; + }); diff --git a/interface/js/app/rspamd.js b/interface/js/app/rspamd.js index 1b90cb27e..e983e4074 100644 --- a/interface/js/app/rspamd.js +++ b/interface/js/app/rspamd.js @@ -22,531 +22,517 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -define(['jquery', 'd3pie', 'visibility', 'app/stats', 'app/graph', 'app/config', - 'app/symbols', 'app/history', 'app/upload'], - function ($, d3pie, visibility, tab_stat, tab_graph, tab_config, - tab_symbols, tab_history, tab_upload) { - // begin - var graphs = {}; - var tables = {}; - var neighbours = []; //list of clusters - var checked_server = "All SERVERS"; - var interface = { - read_only: false, - }; - - var timer_id = []; - var selData; // Graph's dataset selector state - function stopTimers() { - for (var key in timer_id) { - Visibility.stop(timer_id[key]); - } +/* global jQuery:false, Visibility:false */ + +define(["jquery", "d3pie", "visibility", "app/stats", "app/graph", "app/config", + "app/symbols", "app/history", "app/upload"], +function ($, d3pie, visibility, tab_stat, tab_graph, tab_config, + tab_symbols, tab_history, tab_upload) { + "use strict"; + // begin + var graphs = {}; + var tables = {}; + var neighbours = []; // list of clusters + var checked_server = "All SERVERS"; + var ui = { + read_only: false, + }; + + var timer_id = []; + var selData; // Graph's dataset selector state + + function cleanCredentials() { + sessionStorage.clear(); + $("#statWidgets").empty(); + $("#listMaps").empty(); + $("#modalBody").empty(); + $("#historyLog tbody").remove(); + $("#errorsLog tbody").remove(); + $("#symbolsTable tbody").remove(); + } + + function stopTimers() { + for (var key in timer_id) { + Visibility.stop(timer_id[key]); } + } - function disconnect() { - if (graphs.chart) { - graphs.chart.destroy(); - graphs.chart = undefined; - } - if (graphs.rrd_pie) { - graphs.rrd_pie.destroy(); - graphs.rrd_pie = undefined; - } - if (graphs.graph) { - graphs.graph.destroy(); - graphs.graph = undefined; - } - if (tables.history) { - tables.history.destroy(); - tables.history = undefined; - } - if (tables.errors) { - tables.errors.destroy(); - tables.errors = undefined; - } - if (tables.symbols) { - tables.symbols.destroy(); - tables.symbols = undefined; - } - - stopTimers(); - cleanCredentials(); - interface.connect(); + function disconnect() { + if (graphs.chart) { + graphs.chart.destroy(); + delete graphs.chart; } - - function tabClick(tab_id) { - if ($(tab_id).attr('disabled')) return; - $(tab_id).attr('disabled', true); - - stopTimers(); - - if (tab_id === "#refresh") { - tab_id = "#" + $('.navbar-nav .active > a' ).attr('id'); - } - - switch (tab_id) { - case "#status_nav": - tab_stat.statWidgets(interface, graphs, checked_server); - timer_id.status = Visibility.every(10000, function () { - tab_stat.statWidgets(interface, graphs, checked_server); - }); - break; - case "#throughput_nav": - tab_graph.draw(interface, graphs, neighbours, checked_server, selData); - - var autoRefresh = { - hourly: 60000, - daily: 300000 - }; - timer_id.throughput = Visibility.every(autoRefresh[selData] || 3600000, function () { - tab_graph.draw(interface, graphs, neighbours, checked_server, selData); - }); - break; - case "#configuration_nav": - tab_config.getActions(interface); - tab_config.getMaps(interface); - break; - case "#symbols_nav": - tab_symbols.getSymbols(interface, tables, checked_server); - break; - case "#history_nav": - tab_history.getHistory(interface, tables, neighbours, checked_server); - tab_history.getErrors(interface, tables, neighbours, checked_server); - break; - case "#disconnect": - disconnect(); - break; - } - - setTimeout(function () { - $(tab_id).removeAttr('disabled'); - $('#refresh').removeAttr('disabled'); - }, 1000); + if (graphs.rrd_pie) { + graphs.rrd_pie.destroy(); + delete graphs.rrd_pie; } - - // @return password - function getPassword() { - return sessionStorage.getItem('Password'); + if (graphs.graph) { + graphs.graph.destroy(); + delete graphs.graph; } - - // @save credentials - function saveCredentials(password) { - sessionStorage.setItem('Password', password); + if (tables.history) { + tables.history.destroy(); + delete tables.history; } - - // @clean credentials - function cleanCredentials() { - sessionStorage.clear(); - $('#statWidgets').empty(); - $('#listMaps').empty(); - $('#modalBody').empty(); - $('#historyLog tbody').remove(); - $('#errorsLog tbody').remove(); - $('#symbolsTable tbody').remove(); - password = ''; + if (tables.errors) { + tables.errors.destroy(); + delete tables.errors; } - - function isLogged() { - if (sessionStorage.getItem('Credentials') !== null) { - return true; - } - return false; + if (tables.symbols) { + tables.symbols.destroy(); + delete tables.symbols; } - function displayUI() { - // @toggle auth and main - var disconnect = $('#navBar .pull-right'); - $('#mainUI').show(); - $('#progress').show(); - $(disconnect).show(); - tabClick("#refresh"); - $('#progress').hide(); - } + stopTimers(); + cleanCredentials(); + ui.connect(); + } - function alertMessage(alertClass, alertText) { - const a = $('<div class="alert ' + alertClass + ' alert-dismissible fade in show">' + - '<button type="button" class="close" data-dismiss="alert" title="Dismiss">×</button>' + - '<strong>' + alertText + '</strong>'); - $('.notification-area').append(a); + function tabClick(id) { + var tab_id = id; + if ($(tab_id).attr("disabled")) return; + $(tab_id).attr("disabled", true); - setTimeout(function () { - $(a).fadeTo(500, 0).slideUp(500, function () { - $(this).alert('close'); - }); - }, 5000); - } - - // Public functions - interface.alertMessage = alertMessage; - interface.setup = function() { - $("#selData").change(function () { - selData = this.value; - tabClick("#throughput_nav"); - }); - $.ajaxSetup({ - timeout: 20000, - jsonp: false - }); - - $(document).ajaxStart(function () { - $('#navBar').addClass('loading'); - }); - $(document).ajaxComplete(function () { - setTimeout(function () { - $('#navBar').removeClass('loading'); - }, 1000); - }); + stopTimers(); - $.ajax({ - type: 'GET', - url: 'stat', - success: function () { - saveCredentials({}, 'nopassword'); - var dialog = $('#connectDialog'); - var backdrop = $('#backDrop'); - $(dialog).hide(); - $(backdrop).hide(); - displayUI(); - }, - }); + if (tab_id === "#refresh") { + tab_id = "#" + $(".navbar-nav .active > a").attr("id"); + } - $('a[data-toggle="tab"]').on('click', function (e) { - var tab_id = "#" + $(e.target).attr("id"); - tabClick(tab_id); + switch (tab_id) { + case "#status_nav": + tab_stat.statWidgets(ui, graphs, checked_server); + timer_id.status = Visibility.every(10000, function () { + tab_stat.statWidgets(ui, graphs, checked_server); }); + break; + case "#throughput_nav": + tab_graph.draw(ui, graphs, neighbours, checked_server, selData); - $("#selSrv").change(function () { - checked_server = this.value; - $('#selSrv [value="' + checked_server + '"]').prop("checked", true); - tabClick("#" + $("#navBar ul li.active > a").attr("id")); + var autoRefresh = { + hourly: 60000, + daily: 300000 + }; + timer_id.throughput = Visibility.every(autoRefresh[selData] || 3600000, function () { + tab_graph.draw(ui, graphs, neighbours, checked_server, selData); }); + break; + case "#configuration_nav": + tab_config.getActions(ui, checked_server); + tab_config.getMaps(ui, checked_server); + break; + case "#symbols_nav": + tab_symbols.getSymbols(ui, checked_server); + break; + case "#history_nav": + tab_history.getHistory(ui, tables, neighbours, checked_server); + tab_history.getErrors(ui, tables, neighbours, checked_server); + break; + case "#disconnect": + disconnect(); + break; + } - // Radio buttons - $(document).on('click', 'input:radio[name="clusterName"]', function () { - if (!this.disabled) { - checked_server = this.value; - tabClick("#status_nav"); - } + setTimeout(function () { + $(tab_id).removeAttr("disabled"); + $("#refresh").removeAttr("disabled"); + }, 1000); + } + + // @return password + function getPassword() { + return sessionStorage.getItem("Password"); + } + + // @save credentials + function saveCredentials(password) { + sessionStorage.setItem("Password", password); + } + + function isLogged() { + if (sessionStorage.getItem("Credentials") !== null) { + return true; + } + return false; + } + + function displayUI() { + // @toggle auth and main + var buttons = $("#navBar .pull-right"); + $("#mainUI").show(); + $("#progress").show(); + $(buttons).show(); + tabClick("#refresh"); + $("#progress").hide(); + } + + function alertMessage(alertClass, alertText) { + var a = $("<div class=\"alert " + alertClass + " alert-dismissible fade in show\">" + + "<button type=\"button\" class=\"close\" data-dismiss=\"alert\" title=\"Dismiss\">×</button>" + + "<strong>" + alertText + "</strong>"); + $(".notification-area").append(a); + + setTimeout(function () { + $(a).fadeTo(500, 0).slideUp(500, function () { + $(this).alert("close"); }); - tab_config.setup(interface); - tab_symbols.setup(interface, tables); - tab_history.setup(interface, tables); - tab_upload.setup(interface); - selData = tab_graph.setup(); - }; - - interface.connect = function() { - if (isLogged()) { - var data = JSON.parse(sessionStorage.getItem('Credentials')); - - if (data && data[checked_server].read_only) { - interface.read_only = true; - $('#learning_nav').hide(); - $('#resetHistory').attr('disabled', true); - $('#errors-history').hide(); + }, 5000); + } + + function queryServer(neighbours_status, ind, req_url, o) { + neighbours_status[ind].checked = false; + neighbours_status[ind].data = {}; + neighbours_status[ind].status = false; + var req_params = { + jsonp: false, + data: o.data, + beforeSend: function (xhr) { + xhr.setRequestHeader("Password", getPassword()); + + if (o.headers) { + $.each(o.headers, function (hname, hvalue) { + xhr.setRequestHeader(hname, hvalue); + }); } - else { - interface.read_only = false; - $('#learning_nav').show(); - $('#resetHistory').removeAttr('disabled', true); + }, + url: neighbours_status[ind].url + req_url, + success: function (json) { + neighbours_status[ind].checked = true; + neighbours_status[ind].status = true; + neighbours_status[ind].data = json; + }, + error: function (jqXHR, textStatus, errorThrown) { + neighbours_status[ind].checked = true; + function errorMessage() { + alertMessage("alert-error", neighbours_status[ind].name + " > " + + (o.errorMessage ? o.errorMessage : "Request failed") + + (errorThrown ? (": " + errorThrown) : "")); } - displayUI(); - return; - } - - var ui = $('#mainUI'); - var dialog = $('#connectDialog'); - var backdrop = $('#backDrop'); - $(ui).hide(); - $(dialog).show(); - $(backdrop).show(); - $('#connectPassword').focus(); - $('#connectForm').off('submit'); - - $('#connectForm').on('submit', function (e) { - e.preventDefault(); - var password = $('#connectPassword').val(); - if (!/^[\u0000-\u007f]*$/.test(password)) { - alertMessage('alert-modal alert-error', 'Invalid characters in the password'); - $('#connectPassword').focus(); - return; + if (o.error) { + o.error(neighbours_status[ind], + jqXHR, textStatus, errorThrown); + } else if (o.errorOnceId) { + var alert_status = o.errorOnceId + neighbours_status[ind].name; + if (!(alert_status in sessionStorage)) { + sessionStorage.setItem(alert_status, true); + errorMessage(); + } + } else { + errorMessage(); } - - $.ajax({ - global: false, - jsonp: false, - dataType: 'json', - type: 'GET', - url: 'auth', - beforeSend: function (xhr) { - xhr.setRequestHeader('Password', password); - }, - success: function (data) { - $('#connectPassword').val(''); - if (data.auth === 'failed') { - // Is actually never returned by Rspamd + }, + complete: function () { + if (neighbours_status.every(function (elt) { return elt.checked; })) { + if (neighbours_status.some(function (elt) { return elt.status; })) { + if (o.success) { + o.success(neighbours_status); } else { - if (data.read_only) { - interface.read_only = true; - $('#learning_nav').hide(); - $('#resetHistory').attr('disabled', true); - $('#errors-history').hide(); - } - else { - interface.read_only = false; - $('#learning_nav').show(); - $('#resetHistory').removeAttr('disabled', true); - } - - saveCredentials(password); - $(dialog).hide(); - $(backdrop).hide(); - displayUI(); + alertMessage("alert-success", "Request completed"); } - }, - error: function (data) { - interface.alertMessage('alert-modal alert-error', data.statusText); - $('#connectPassword').val(''); - $('#connectPassword').focus(); - } - }); - }); - }; - - interface.queryLocal = function(req_url, on_success, on_error, method, headers, params) { - var req_params = { - type: method, - jsonp: false, - beforeSend: function (xhr) { - xhr.setRequestHeader("Password", getPassword()); - - if (headers) { - $.each(headers, function(hname, hvalue){ - xhr.setRequestHeader(hname, hvalue); - }); - } - }, - url: req_url, - success: function (data) { - if (on_success) { - on_success(data); - } - else { - alertMessage('alert-success', 'Data saved'); - } - }, - error: function(jqXHR, textStatus, errorThrown) { - if (on_error) { - on_error('local', jqXHR, textStatus, errorThrown); - } - else { - alertMessage('alert-error', 'Cannot receive data: ' + errorThrown); + } else { + alertMessage("alert-error", "Request failed"); } } - }; - if (params) { - $.each(params, function(k, v) { - req_params[k] = v; - }); } - $.ajax(req_params); + }; + if (o.method) { + req_params.method = o.method; + } + if (o.params) { + $.each(o.params, function (k, v) { + req_params[k] = v; + }); + } + $.ajax(req_params); + } + + // Public functions + ui.alertMessage = alertMessage; + ui.setup = function () { + $("#selData").change(function () { + selData = this.value; + tabClick("#throughput_nav"); + }); + $.ajaxSetup({ + timeout: 20000, + jsonp: false + }); + + $(document).ajaxStart(function () { + $("#navBar").addClass("loading"); + }); + $(document).ajaxComplete(function () { + setTimeout(function () { + $("#navBar").removeClass("loading"); + }, 1000); + }); + + $("a[data-toggle=\"tab\"]").on("click", function (e) { + var tab_id = "#" + $(e.target).attr("id"); + tabClick(tab_id); + }); + + $("#selSrv").change(function () { + checked_server = this.value; + $("#selSrv [value=\"" + checked_server + "\"]").prop("checked", true); + tabClick("#" + $("#navBar ul li.active > a").attr("id")); + }); + + // Radio buttons + $(document).on("click", "input:radio[name=\"clusterName\"]", function () { + if (!this.disabled) { + checked_server = this.value; + tabClick("#status_nav"); + } + }); + tab_config.setup(ui, checked_server); + tab_symbols.setup(ui); + tab_upload.setup(ui); + selData = tab_graph.setup(); + }; + + ui.connect = function () { + if (isLogged()) { + var data = JSON.parse(sessionStorage.getItem("Credentials")); + + if (data && data[checked_server].read_only) { + ui.read_only = true; + $("#learning_nav").hide(); + $("#resetHistory").attr("disabled", true); + $("#errors-history").hide(); + } else { + ui.read_only = false; + $("#learning_nav").show(); + $("#resetHistory").removeAttr("disabled", true); + } + displayUI(); + return; } - interface.queryNeighbours = function(req_url, on_success, on_error, method, headers, params, req_data) { + var dialog = $("#connectDialog"); + var backdrop = $("#backDrop"); + $("#mainUI").hide(); + $(dialog).show(); + $(backdrop).show(); + $("#connectPassword").focus(); + $("#connectForm").off("submit"); + + $("#connectForm").on("submit", function (e) { + e.preventDefault(); + var password = $("#connectPassword").val(); + if (!(/^[\u0020-\u007e]*$/).test(password)) { + alertMessage("alert-modal alert-error", "Invalid characters in the password"); + $("#connectPassword").focus(); + return; + } + $.ajax({ + global: false, + jsonp: false, dataType: "json", type: "GET", - url: "neighbours", - jsonp: false, + url: "auth", beforeSend: function (xhr) { - xhr.setRequestHeader("Password", getPassword()); + xhr.setRequestHeader("Password", password); }, - success: function (data) { + success: function (json) { + $("#connectPassword").val(""); + if (json.auth === "failed") { + // Is actually never returned by Rspamd + } else { + if (json.read_only) { + ui.read_only = true; + $("#learning_nav").hide(); + $("#resetHistory").attr("disabled", true); + $("#errors-history").hide(); + } else { + ui.read_only = false; + $("#learning_nav").show(); + $("#resetHistory").removeAttr("disabled", true); + } + + saveCredentials(password); + $(dialog).hide(); + $(backdrop).hide(); + displayUI(); + } + }, + error: function (jqXHR) { + ui.alertMessage("alert-modal alert-error", jqXHR.statusText); + $("#connectPassword").val(""); + $("#connectPassword").focus(); + } + }); + }); + }; + + ui.drawPie = function (object, id, data, conf) { + var obj = object; + if (obj) { + obj.updateProp("data.content", + data.filter(function (elt) { + return elt.value > 0; + }) + ); + } else { + obj = new d3pie(id, + $.extend({}, { + header: { + title: { + text: "Rspamd filter stats", + fontSize: 24, + font: "open sans" + }, + subtitle: { + color: "#999999", + fontSize: 12, + font: "open sans" + }, + titleSubtitlePadding: 9 + }, + footer: { + color: "#999999", + fontSize: 10, + font: "open sans", + location: "bottom-left" + }, + size: { + canvasWidth: 600, + canvasHeight: 400, + pieInnerRadius: "20%", + pieOuterRadius: "85%" + }, + data: { + // "sortOrder": "value-desc", + content: data.filter(function (elt) { + return elt.value > 0; + }) + }, + labels: { + outer: { + hideWhenLessThanPercentage: 1, + pieDistance: 30 + }, + inner: { + hideWhenLessThanPercentage: 4 + }, + mainLabel: { + fontSize: 14 + }, + percentage: { + color: "#eeeeee", + fontSize: 14, + decimalPlaces: 0 + }, + lines: { + enabled: true + }, + truncation: { + enabled: true + } + }, + tooltips: { + enabled: true, + type: "placeholder", + string: "{label}: {value} ({percentage}%)" + }, + effects: { + pullOutSegmentOnClick: { + effect: "back", + speed: 400, + size: 8 + }, + load: { + effect: "none" + } + }, + misc: { + gradient: { + enabled: true, + percentage: 100 + } + } + }, conf)); + } + return obj; + }; + + ui.getPassword = getPassword; + + /** + * @param {string} url - A string containing the URL to which the request is sent + * @param {Object} [options] - A set of key/value pairs that configure the Ajax request. All settings are optional. + * + * @param {Object|string|Array} [options.data] - Data to be sent to the server. + * @param {Function} [options.error] - A function to be called if the request fails. + * @param {string} [options.errorMessage] - Text to display in the alert message if the request fails. + * @param {string} [options.errorOnceId] - A prefix of the alert ID to be added to the session storage. If the + * parameter is set, the error for each server will be displayed only once per session. + * @param {Object} [options.headers] - An object of additional header key/value pairs to send along with requests + * using the XMLHttpRequest transport. + * @param {string} [options.method] - The HTTP method to use for the request. + * @param {Object} [options.params] - An object of additional jQuery.ajax() settings key/value pairs. + * @param {string} [options.server] - A server to which send the request. + * @param {Function} [options.success] - A function to be called if the request succeeds. + * + * @returns {undefined} + */ + ui.query = function (url, options) { + // Force options to be an object + var o = options || {}; + Object.keys(o).forEach(function (option) { + if (["data", "error", "errorMessage", "errorOnceId", "headers", "method", "params", "server", "success"] + .indexOf(option) < 0) { + throw new Error("Unknown option: " + option); + } + }); + + var neighbours_status = [{ + name: "local", + host: "local", + url: "", + }]; + o.server = o.server || checked_server; + if (o.server === "All SERVERS") { + queryServer(neighbours_status, 0, "neighbours", { + success: function (json) { + var data = json[0].data; if (jQuery.isEmptyObject(data)) { neighbours = { - local: { - host: window.location.host, - url: window.location.href - } + local: { + host: window.location.host, + url: window.location.href + } }; - } else { + } else { neighbours = data; } - var neighbours_status = []; + neighbours_status = []; $.each(neighbours, function (ind) { neighbours_status.push({ name: ind, - url: neighbours[ind].url, host: neighbours[ind].host, - checked: false, - data: {}, - status: false, + url: neighbours[ind].url, }); }); $.each(neighbours_status, function (ind) { - "use strict"; - method = typeof method !== 'undefined' ? method : "GET"; - var req_params = { - type: method, - jsonp: false, - data: req_data, - beforeSend: function (xhr) { - xhr.setRequestHeader("Password", getPassword()); - - if (headers) { - $.each(headers, function(hname, hvalue){ - xhr.setRequestHeader(hname, hvalue); - }); - } - }, - url: neighbours_status[ind].url + req_url, - success: function (data) { - neighbours_status[ind].checked = true; - - if (jQuery.isEmptyObject(data)) { - neighbours_status[ind].status = false; //serv does not work - } else { - neighbours_status[ind].status = true; //serv does not work - neighbours_status[ind].data = data; - } - if (neighbours_status.every(function (elt) {return elt.checked;})) { - if (on_success) { - on_success(neighbours_status); - } - else { - alertMessage('alert-success', 'Request completed'); - } - } - }, - error: function(jqXHR, textStatus, errorThrown) { - neighbours_status[ind].status = false; - neighbours_status[ind].checked = true; - if (on_error) { - on_error(neighbours_status[ind], - jqXHR, textStatus, errorThrown); - } - else { - alertMessage('alert-error', 'Cannot receive data from ' + - neighbours_status[ind].host + ': ' + errorThrown); - } - if (neighbours_status.every( - function (elt) {return elt.checked;})) { - if (on_success) { - on_success(neighbours_status); - } - else { - alertMessage('alert-success', 'Request completed'); - } - } - } - //error display - }; - if (params) { - $.each(params, function(k, v) { - req_params[k] = v; - }); - } - $.ajax(req_params); + queryServer(neighbours_status, ind, url, o); }); }, - error: function () { - interface.alertMessage('alert-error', 'Cannot receive neighbours data'); - }, + errorMessage: "Cannot receive neighbours data" }); - }; - - interface.drawPie = function(obj, id, data, conf) { - if (obj) { - obj.updateProp("data.content", - data.filter(function (elt) { - return elt.value > 0; - }) - ); - } else { - obj = new d3pie(id, - $.extend({}, { - "header": { - "title": { - "text": "Rspamd filter stats", - "fontSize": 24, - "font": "open sans" - }, - "subtitle": { - "color": "#999999", - "fontSize": 12, - "font": "open sans" - }, - "titleSubtitlePadding": 9 - }, - "footer": { - "color": "#999999", - "fontSize": 10, - "font": "open sans", - "location": "bottom-left" - }, - "size": { - "canvasWidth": 600, - "canvasHeight": 400, - "pieInnerRadius": "20%", - "pieOuterRadius": "85%" - }, - "data": { - //"sortOrder": "value-desc", - "content": data.filter(function (elt) { - return elt.value > 0; - }) - }, - "labels": { - "outer": { - "hideWhenLessThanPercentage": 1, - "pieDistance": 30 - }, - "inner": { - "hideWhenLessThanPercentage": 4 - }, - "mainLabel": { - "fontSize": 14 - }, - "percentage": { - "color": "#eeeeee", - "fontSize": 14, - "decimalPlaces": 0 - }, - "lines": { - "enabled": true - }, - "truncation": { - "enabled": true - } - }, - "tooltips": { - "enabled": true, - "type": "placeholder", - "string": "{label}: {value} ({percentage}%)" - }, - "effects": { - "pullOutSegmentOnClick": { - "effect": "back", - "speed": 400, - "size": 8 - }, - "load": { - "effect": "none" - } - }, - "misc": { - "gradient": { - "enabled": true, - "percentage": 100 - } - } - }, conf)); + } else { + if (o.server !== "local") { + neighbours_status = [{ + name: o.server, + host: neighbours[o.server].host, + url: neighbours[o.server].url, + }]; } - return obj; - }; - - interface.getPassword = getPassword; + queryServer(neighbours_status, 0, url, o); + } + }; - return interface; + return ui; }); diff --git a/interface/js/app/stats.js b/interface/js/app/stats.js index 3855f4d64..e40206266 100644 --- a/interface/js/app/stats.js +++ b/interface/js/app/stats.js @@ -22,216 +22,211 @@ THE SOFTWARE. */ -define(['jquery', 'd3pie', 'humanize'], -function($, d3pie, Humanize) { - // @ ms to date - function msToTime(seconds) { - years = seconds / 31536000 >> 0; // 3600*24*365 - months = seconds % 31536000 / 2628000 >> 0; //3600*24*365/12 - days = seconds % 31536000 % 2628000 / 86400 >> 0; //24*3600 - hours = seconds % 31536000 % 2628000 % 86400 / 3600 >> 0; - minutes = seconds % 31536000 % 2628000 % 86400 % 3600 / 60 >> 0; +define(["jquery", "d3pie", "humanize"], + function ($, d3pie, Humanize) { + "use strict"; + // @ ms to date + function msToTime(seconds) { + /* eslint-disable no-bitwise */ + var years = seconds / 31536000 >> 0; // 3600*24*365 + var months = seconds % 31536000 / 2628000 >> 0; // 3600*24*365/12 + var days = seconds % 31536000 % 2628000 / 86400 >> 0; // 24*3600 + var hours = seconds % 31536000 % 2628000 % 86400 / 3600 >> 0; + var minutes = seconds % 31536000 % 2628000 % 86400 % 3600 / 60 >> 0; + /* eslint-enable no-bitwise */ + var out; if (years > 0) { - if (months > 0) { - out = years + 'yr ' + months + 'mth'; - } else { - out = years + 'yr ' + days + 'd'; - } + if (months > 0) { + out = years + "yr " + months + "mth"; + } else { + out = years + "yr " + days + "d"; + } } else if (months > 0) { - out = months + 'mth ' + days + 'd'; + out = months + "mth " + days + "d"; } else if (days > 0) { - out = days + 'd ' + hours + 'hr'; + out = days + "d " + hours + "hr"; } else if (hours > 0) { - out = hours + 'hr ' + minutes + 'min'; + out = hours + "hr " + minutes + "min"; } else { - out = minutes + 'min'; + out = minutes + "min"; } return out; - } - - function displayStatWidgets(checked_server) { - var widgets = $('#statWidgets'); - $(widgets).empty().hide(); - - var servers = JSON.parse(sessionStorage.getItem('Credentials')); - var data = {}; - - if (servers && servers[checked_server]) { - data = servers[checked_server].data; } - var stat_w = []; - - $.each(data, function (i, item) { - var widget = ''; - if (i == 'auth') {} - else if (i == 'error') {} - else if (i == 'version') { - widget = '<div class="left"><strong>' + item + '</strong>' + - i + '</div>'; - $(widget).appendTo(widgets); - } else if (i == 'uptime') { - widget = '<div class="right"><strong>' + msToTime(item) + - '</strong>' + i + '</div>'; - $(widget).appendTo(widgets); - } else { - var titleAtt = Humanize.intComma(item) + ' ' + i; - widget = '<li class="stat-box"><div class="widget" title="' + titleAtt + '"><strong>' + - Humanize.compactInteger(item) + '</strong>' + i + '</div></li>'; - if (i == 'scanned') { - stat_w[0] = widget; - } else if (i == 'clean') { - stat_w[1] = widget; - } else if (i == 'greylist') { - stat_w[2] = widget; - } else if (i == 'probable') { - stat_w[3] = widget; - } else if (i == 'reject') { - stat_w[4] = widget; - } else if (i == 'learned') { - stat_w[5] = widget; - } - } - }); - $.each(stat_w, function (i, item) { - $(item).appendTo(widgets); - }); - $('#statWidgets .left,#statWidgets .right').wrapAll('<li class="stat-box pull-right"><div class="widget"></div></li>'); - $('#statWidgets').find('li.pull-right').appendTo('#statWidgets'); - - $("#clusterTable tbody").empty(); - $("#selSrv").empty(); - $.each(servers, function (key, val) { - var glyph_status; - var short_id; - if (!('config_id' in val.data)) { - val.data.config_id = ""; - } - if (val.status) { - glyph_status = "glyphicon glyphicon-ok-circle"; - short_id = val.data.config_id.substring(0, 8); - } - else { - glyph_status = "glyphicon glyphicon-remove-circle"; - short_id = "???"; - } - $('#clusterTable tbody').append('<tr>' + - '<td class="col1" title="Radio"><input type="radio" class="form-control radio" name="clusterName" value="' + key + '"></td>' + - '<td class="col2" title="SNAme">' + key + '</td>' + - '<td class="col3" title="SHost">' + val.host + '</td>' + - '<td class="col4" title="SStatus"><span class="icon"><i class="' + glyph_status + '"></i></span></td>' + - '<td class="col5" title="short_id">' + short_id + '</td></tr>'); + function displayStatWidgets(checked_server) { + var widgets = $("#statWidgets"); + $(widgets).empty().hide(); - $("#selSrv").append( $('<option value="' + key + '">' + key + '</option>')); + var servers = JSON.parse(sessionStorage.getItem("Credentials")); + var data = {}; - if (checked_server == key) { - $('#clusterTable tbody [value="' + key + '"]').prop("checked", true); - $('#selSrv [value="' + key + '"]').prop("selected", true); - } - else if (!val.status) { - $('#clusterTable tbody [value="' + key + '"]').prop("disabled", true); - $('#selSrv [value="' + key + '"]').prop("disabled", true); + if (servers && servers[checked_server]) { + data = servers[checked_server].data; } - }); - $(widgets).show(); - } - - function getChart(rspamd, pie, checked_server) { - var creds = JSON.parse(sessionStorage.getItem('Credentials')); - if (creds && creds[checked_server]) { - var data = creds[checked_server].data; - var new_data = [ { - "color" : "#66CC00", - "label" : "Clean", - "data" : data.clean, - "value" : data.clean - }, { - "color" : "#BF8040", - "label" : "Temporarily rejected", - "data" : data.soft_reject, - "value" : data.soft_reject - }, { - "color" : "#FFAD00", - "label" : "Probable spam", - "data" : data.probable, - "value" : data.probable - }, { - "color" : "#436EEE", - "label" : "Greylisted", - "data" : data.greylist, - "value" : data.greylist - }, { - "color" : "#FF0000", - "label" : "Rejected", - "data" : data.reject, - "value" : data.reject - } ]; - - return rspamd.drawPie(pie, "chart", new_data); - } - } - // Public API - var interface = { - statWidgets: function(rspamd, graphs, checked_server) { - rspamd.queryNeighbours("/auth", function(neighbours_status) { - var neighbours_sum = { - version: neighbours_status[0].data.version, - auth: "ok", - uptime: 0, - clean: 0, - probable: 0, - greylist: 0, - reject: 0, - soft_reject: 0, - scanned: 0, - learned: 0, - read_only: neighbours_status[0].data.read_only, - config_id: "" - }; - var status_count = 0; - for(var e in neighbours_status) { - if(neighbours_status[e].status === true) { - // Remove alert status - localStorage.removeItem(e + '_alerted'); - neighbours_sum.clean += neighbours_status[e].data.clean; - neighbours_sum.probable += neighbours_status[e].data.probable; - neighbours_sum.greylist += neighbours_status[e].data.greylist; - neighbours_sum.reject += neighbours_status[e].data.reject; - neighbours_sum.soft_reject += neighbours_status[e].data.soft_reject; - neighbours_sum.scanned += neighbours_status[e].data.scanned; - neighbours_sum.learned += neighbours_status[e].data.learned; - neighbours_sum.uptime += neighbours_status[e].data.uptime; - status_count++; + var stat_w = []; + + $.each(data, function (i, item) { + var widget = ""; + if (i === "auth" || i === "error") { return true; } // Skip to the next iteration + if (i === "version") { + widget = "<div class=\"left\"><strong>" + item + "</strong>" + + i + "</div>"; + $(widget).appendTo(widgets); + } else if (i === "uptime") { + widget = "<div class=\"right\"><strong>" + msToTime(item) + + "</strong>" + i + "</div>"; + $(widget).appendTo(widgets); + } else { + var titleAtt = Humanize.intComma(item) + " " + i; + widget = "<li class=\"stat-box\"><div class=\"widget\" title=\"" + titleAtt + "\"><strong>" + + Humanize.compactInteger(item) + "</strong>" + i + "</div></li>"; + if (i === "scanned") { + stat_w[0] = widget; + } else if (i === "clean") { + stat_w[1] = widget; + } else if (i === "greylist") { + stat_w[2] = widget; + } else if (i === "probable") { + stat_w[3] = widget; + } else if (i === "reject") { + stat_w[4] = widget; + } else if (i === "learned") { + stat_w[5] = widget; } } - neighbours_sum.uptime = Math.floor(neighbours_sum.uptime / status_count); - var to_Credentials = {}; - to_Credentials["All SERVERS"] = { name: "All SERVERS", - url: "", - host: "", - checked: true, - data: neighbours_sum, - status: true - }; - neighbours_status.forEach(function (elmt) { - to_Credentials[elmt.name] = elmt; - }); - sessionStorage.setItem("Credentials", JSON.stringify(to_Credentials)); - displayStatWidgets(checked_server); - graphs.chart = getChart(rspamd, graphs.chart, checked_server); - }, - function (serv, jqXHR, textStatus, errorThrown) { - var alert_status = serv.name + '_alerted'; + }); + $.each(stat_w, function (i, item) { + $(item).appendTo(widgets); + }); + $("#statWidgets .left,#statWidgets .right").wrapAll("<li class=\"stat-box pull-right\"><div class=\"widget\"></div></li>"); + $("#statWidgets").find("li.pull-right").appendTo("#statWidgets"); + + $("#clusterTable tbody").empty(); + $("#selSrv").empty(); + $.each(servers, function (key, val) { + var glyph_status = "glyphicon glyphicon-remove-circle"; + var short_id = "???"; + if (!("config_id" in val.data)) { + val.data.config_id = ""; + } + if (val.status) { + glyph_status = "glyphicon glyphicon-ok-circle"; + short_id = val.data.config_id.substring(0, 8); + } - if (!(alert_status in sessionStorage)) { - sessionStorage.setItem(alert_status, true); - rspamd.alertMessage('alert-error', 'Cannot receive stats data from: ' + - serv.name + ', error: ' + errorThrown); + $("#clusterTable tbody").append("<tr>" + + "<td class=\"col1\" title=\"Radio\"><input type=\"radio\" class=\"form-control radio\" name=\"clusterName\" value=\"" + key + "\"></td>" + + "<td class=\"col2\" title=\"SNAme\">" + key + "</td>" + + "<td class=\"col3\" title=\"SHost\">" + val.host + "</td>" + + "<td class=\"col4\" title=\"SStatus\"><span class=\"icon\"><i class=\"" + glyph_status + "\"></i></span></td>" + + "<td class=\"col5\" title=\"short_id\">" + short_id + "</td></tr>"); + + $("#selSrv").append($("<option value=\"" + key + "\">" + key + "</option>")); + + if (checked_server === key) { + $("#clusterTable tbody [value=\"" + key + "\"]").prop("checked", true); + $("#selSrv [value=\"" + key + "\"]").prop("selected", true); + } else if (!val.status) { + $("#clusterTable tbody [value=\"" + key + "\"]").prop("disabled", true); + $("#selSrv [value=\"" + key + "\"]").prop("disabled", true); } }); - }, - }; + $(widgets).show(); + } - return interface; -} + function getChart(rspamd, pie, checked_server) { + var creds = JSON.parse(sessionStorage.getItem("Credentials")); + if (creds && creds[checked_server]) { + var data = creds[checked_server].data; + var new_data = [{ + color : "#66CC00", + label : "Clean", + data : data.clean, + value : data.clean + }, { + color : "#BF8040", + label : "Temporarily rejected", + data : data.soft_reject, + value : data.soft_reject + }, { + color : "#FFAD00", + label : "Probable spam", + data : data.probable, + value : data.probable + }, { + color : "#436EEE", + label : "Greylisted", + data : data.greylist, + value : data.greylist + }, { + color : "#FF0000", + label : "Rejected", + data : data.reject, + value : data.reject + }]; + + return rspamd.drawPie(pie, "chart", new_data); + } + } + // Public API + var ui = { + statWidgets: function (rspamd, graphs, checked_server) { + rspamd.query("auth", { + success: function (neighbours_status) { + var neighbours_sum = { + version: neighbours_status[0].data.version, + auth: "ok", + uptime: 0, + clean: 0, + probable: 0, + greylist: 0, + reject: 0, + soft_reject: 0, + scanned: 0, + learned: 0, + read_only: neighbours_status[0].data.read_only, + config_id: "" + }; + var status_count = 0; + for (var e in neighbours_status) { + if (neighbours_status[e].status === true) { + // Remove alert status + localStorage.removeItem(e + "_alerted"); + neighbours_sum.clean += neighbours_status[e].data.clean; + neighbours_sum.probable += neighbours_status[e].data.probable; + neighbours_sum.greylist += neighbours_status[e].data.greylist; + neighbours_sum.reject += neighbours_status[e].data.reject; + neighbours_sum.soft_reject += neighbours_status[e].data.soft_reject; + neighbours_sum.scanned += neighbours_status[e].data.scanned; + neighbours_sum.learned += neighbours_status[e].data.learned; + neighbours_sum.uptime += neighbours_status[e].data.uptime; + status_count++; + } + } + neighbours_sum.uptime = Math.floor(neighbours_sum.uptime / status_count); + var to_Credentials = {}; + to_Credentials["All SERVERS"] = { + name: "All SERVERS", + url: "", + host: "", + checked: true, + data: neighbours_sum, + status: true + }; + neighbours_status.forEach(function (elmt) { + to_Credentials[elmt.name] = elmt; + }); + sessionStorage.setItem("Credentials", JSON.stringify(to_Credentials)); + displayStatWidgets(checked_server); + graphs.chart = getChart(rspamd, graphs.chart, checked_server); + }, + errorMessage: "Cannot receive stats data", + errorOnceId: "alerted_stats_", + server: "All SERVERS" + }); + }, + }; + + return ui; + } ); diff --git a/interface/js/app/symbols.js b/interface/js/app/symbols.js index 7983ccc7f..04b661d99 100644 --- a/interface/js/app/symbols.js +++ b/interface/js/app/symbols.js @@ -22,265 +22,241 @@ THE SOFTWARE. */ -define(['jquery', 'footable'], -function($) { - var interface = {} - var ft = {} +/* global FooTable:false */ - function saveSymbols(rspamd, action, id, is_cluster) { - var inputs = $('#' + id + ' :input[data-role="numerictextbox"]'); - var url = action; - var values = []; - $(inputs).each(function () { - values.push({ - name: $(this).attr('id').substring(5), - value: parseFloat($(this).val()) - }); - }); +define(["jquery", "footable"], + function ($) { + "use strict"; + var ft = {}; + var ui = {}; - if (is_cluster) { - rspamd.queryNeighbours(url, function () { - rspamd.alertMessage('alert-modal alert-success', 'Symbols successfully saved'); - }, function (serv, qXHR, textStatus, errorThrown) { - rspamd.alertMessage('alert-modal alert-error', - 'Save symbols error on ' + - serv.name + ': ' + errorThrown); - }, "POST", {}, { - data: JSON.stringify(values), - dataType: "json", - }); + function getSelector(id) { + var e = document.getElementById(id); + return e.options[e.selectedIndex].value; } - else { - $.ajax({ - data: JSON.stringify(values), - dataType: 'json', - type: 'POST', - url: url, - jsonp: false, - beforeSend: function (xhr) { - xhr.setRequestHeader('Password', rspamd.getPassword()); - }, + + function saveSymbols(rspamd, action, id, server) { + var inputs = $("#" + id + " :input[data-role=\"numerictextbox\"]"); + var url = action; + var values = []; + $(inputs).each(function () { + values.push({ + name: $(this).attr("id").substring(5), + value: parseFloat($(this).val()) + }); + }); + + rspamd.query(url, { success: function () { - rspamd.alertMessage('alert-modal alert-success', 'Symbols successfully saved'); + rspamd.alertMessage("alert-modal alert-success", "Symbols successfully saved"); }, - error: function (data) { - rspamd.alertMessage('alert-modal alert-error', data.statusText); - } + errorMessage: "Save symbols error", + method: "POST", + params: { + data: JSON.stringify(values), + dataType: "json", + }, + server: server }); } - } - function decimalStep(number) { - var digits = ((+number).toFixed(20)).replace(/^-?\d*\.?|0+$/g, '').length; - if (digits === 0 || digits > 4) { - return 0.1; - } else { - return 1.0 / (Math.pow(10, digits)); + function decimalStep(number) { + var digits = ((Number(number)).toFixed(20)).replace(/^-?\d*\.?|0+$/g, "").length; + return (digits === 0 || digits > 4) ? 0.1 : 1.0 / (Math.pow(10, digits)); } - } - function process_symbols_data(data) { - var items = []; - var lookup = {}; - var freqs = []; - var distinct_groups = []; + function process_symbols_data(data) { + var items = []; + var lookup = {}; + var freqs = []; + var distinct_groups = []; + var selected_server = getSelector("selSrv"); - $.each(data, function (i, group) { - $.each(group.rules, function (i, item) { - var max = 20; - var min = -20; - if (item.weight > max) { - max = item.weight * 2; - } - item.group = group.group - if (item.weight < min) { - min = item.weight * 2; - } - var label_class = ''; - if (item.weight < 0) { - label_class = 'scorebar-ham'; - } else if (item.weight > 0) { - label_class = 'scorebar-spam'; - } - item.weight = '<input class="form-control input-sm mb-disabled ' + label_class + - '" data-role="numerictextbox" autocomplete="off" "type="number" class="input" min="' + - min + '" max="' + - max + '" step="' + decimalStep(item.weight) + - '" tabindex="1" value="' + Number(item.weight).toFixed(3) + - '" id="_sym_' + item.symbol + '"></input>' - if (!item.time) { - item.time = 0; - } - item.time = Number(item.time).toFixed(2) + 's' - if (!item.frequency) { - item.frequency = 0; - } - freqs.push(item.frequency); - item.frequency = Number(item.frequency).toFixed(2) - if (!(item.group in lookup)) { - lookup[item.group] = 1; - distinct_groups.push(item.group); - } - item.save = '<button type="button" data-save="local" class="btn btn-primary btn-sm mb-disabled">Save</button>' + - ' <button data-save="cluster" type="button" class="btn btn-primary btn-sm mb-disabled">Save in cluster</button>'; - items.push(item) + data.forEach(function (group) { + group.rules.forEach(function (item) { + var max = 20; + var min = -20; + if (item.weight > max) { + max = item.weight * 2; + } + item.group = group.group; + if (item.weight < min) { + min = item.weight * 2; + } + var label_class = ""; + if (item.weight < 0) { + label_class = "scorebar-ham"; + } else if (item.weight > 0) { + label_class = "scorebar-spam"; + } + item.weight = "<input class=\"form-control input-sm mb-disabled " + label_class + + "\" data-role=\"numerictextbox\" autocomplete=\"off\" \"type=\"number\" class=\"input\" min=\"" + + min + "\" max=\"" + + max + "\" step=\"" + decimalStep(item.weight) + + "\" tabindex=\"1\" value=\"" + Number(item.weight).toFixed(3) + + "\" id=\"_sym_" + item.symbol + "\"></input>"; + if (!item.time) { + item.time = 0; + } + item.time = Number(item.time).toFixed(2) + "s"; + if (!item.frequency) { + item.frequency = 0; + } + freqs.push(item.frequency); + item.frequency = Number(item.frequency).toFixed(2); + if (!(item.group in lookup)) { + lookup[item.group] = 1; + distinct_groups.push(item.group); + } + item.save = + "<button data-save=\"" + selected_server + + "\" title=\"Save changes to the selected server\" " + + "type=\"button\" class=\"btn btn-primary btn-sm mb-disabled\">Save</button> " + + "<button data-save=\"All SERVERS" + + "\" title=\"Save changes to all servers\" " + + "type=\"button\" class=\"btn btn-primary btn-sm mb-disabled\">Save in cluster</button>"; + items.push(item); + }); }); - }); - // For better mean calculations - var avg_freq = freqs.sort(function(a, b) { - return Number(a) < Number(b); - }).reduce(function(f1, acc) { - return f1 + acc; - }) / (freqs.length != 0 ? freqs.length : 1.0); - var mult = 1.0; - var exp = 0.0; - - if (avg_freq > 0.0) { - while (mult * avg_freq < 1.0) { - mult *= 10; - exp ++; - } - } - $.each(items, function (i, item) { - item.frequency = Number(item.frequency) * mult; + // For better mean calculations + var avg_freq = freqs.sort(function (a, b) { + return Number(a) < Number(b); + }).reduce(function (f1, acc) { + return f1 + acc; + }) / (freqs.length !== 0 ? freqs.length : 1.0); + var mult = 1.0; + var exp = 0.0; - if (exp > 0) { - item.frequency = item.frequency.toFixed(2) + 'e-' + exp; - } - else { - item.frequency = item.frequency.toFixed(2); + if (avg_freq > 0.0) { + while (mult * avg_freq < 1.0) { + mult *= 10; + exp++; + } } - }); - return [items, distinct_groups] - } - // @get symbols into modal form - interface.getSymbols = function(rspamd, tables, checked_server) { + $.each(items, function (i, item) { + item.frequency = Number(item.frequency) * mult; - $.ajax({ - dataType: 'json', - type: 'GET', - url: 'symbols', - jsonp: false, - beforeSend: function (xhr) { - xhr.setRequestHeader('Password', rspamd.getPassword()); - }, - success: function (data) { - var items = process_symbols_data(data); - FooTable.groupFilter = FooTable.Filtering.extend({ - construct : function(instance) { - this._super(instance); - this.groups = items[1]; - this.def = 'Any group'; - this.$group = null; - }, - $create : function() { - this._super(); - var self = this, $form_grp = $('<div/>', { - 'class' : 'form-group' - }).append($('<label/>', { - 'class' : 'sr-only', - text : 'Group' - })).prependTo(self.$form); + if (exp > 0) { + item.frequency = item.frequency.toFixed(2) + "e-" + exp; + } else { + item.frequency = item.frequency.toFixed(2); + } + }); + return [items, distinct_groups]; + } + // @get symbols into modal form + ui.getSymbols = function (rspamd, checked_server) { + rspamd.query("symbols", { + success: function (json) { + var data = json[0].data; + var items = process_symbols_data(data); + FooTable.groupFilter = FooTable.Filtering.extend({ + construct : function (instance) { + this._super(instance); + this.groups = items[1]; + this.def = "Any group"; + this.$group = null; + }, + $create : function () { + this._super(); + var self = this, $form_grp = $("<div/>", { + class : "form-group" + }).append($("<label/>", { + class : "sr-only", + text : "Group" + })).prependTo(self.$form); - self.$group = $('<select/>', { - 'class' : 'form-control' - }).on('change', { - self : self - }, self._onStatusDropdownChanged).append( - $('<option/>', { - text : self.def - })).appendTo($form_grp); + self.$group = $("<select/>", { + class : "form-control" + }).on("change", { + self : self + }, self._onStatusDropdownChanged).append( + $("<option/>", { + text : self.def + })).appendTo($form_grp); - $.each(self.groups, function(i, group) { - self.$group.append($('<option/>').text(group)); - }); - }, - _onStatusDropdownChanged : function(e) { - var self = e.data.self, selected = $(this).val(); - if (selected !== self.def) { - self.addFilter('group', selected, [ 'group' ]); - } else { - self.removeFilter('group'); - } - self.filter(); - }, - draw : function() { - this._super(); - var group = this.find('group'); - if (group instanceof FooTable.Filter) { - this.$group.val(group.query.val()); - } else { - this.$group.val(this.def); - } - } - }); - ft.symbols = FooTable.init("#symbolsTable", { - "columns": [ - {"sorted": true,"direction": "ASC", "name":"group","title":"Group","style":{"font-size":"11px"}}, - {"name":"symbol","title":"Symbol","style":{"font-size":"11px"}}, - {"name":"description","title":"Description","breakpoints":"xs sm","style":{"font-size":"11px"}}, - {"name":"weight","title":"Score","style":{"font-size":"11px"}}, - {"name":"frequency","title":"Frequency","breakpoints":"xs sm","style":{"font-size":"11px"}}, - {"name":"time","title":"Avg. time","breakpoints":"xs sm","style":{"font-size":"11px"}}, - {"name":"save","title":"Save","style":{"font-size":"11px"}}, - ], - "rows": items[0], - "paging": { - "enabled": true, - "limit": 5, - "size": 25 - }, - "filtering": { - "enabled": true, - "position": "left", - "connectors": false - }, - "sorting": { - "enabled": true - }, - components: { - filtering: FooTable.groupFilter - }, - "on": { - "ready.ft.table": function () { - if (rspamd.read_only) { - $(".mb-disabled").attr('disabled', true); + $.each(self.groups, function (i, group) { + self.$group.append($("<option/>").text(group)); + }); + }, + _onStatusDropdownChanged : function (e) { + var self = e.data.self, selected = $(this).val(); + if (selected !== self.def) { + self.addFilter("group", selected, ["group"]); + } else { + self.removeFilter("group"); + } + self.filter(); + }, + draw : function () { + this._super(); + var group = this.find("group"); + if (group instanceof FooTable.Filter) { + this.$group.val(group.query.val()); + } else { + this.$group.val(this.def); + } } - } - } + }); + ft.symbols = FooTable.init("#symbolsTable", { + columns: [ + {sorted: true, direction: "ASC", name:"group", title:"Group", style:{"font-size":"11px"}}, + {name:"symbol", title:"Symbol", style:{"font-size":"11px"}}, + {name:"description", title:"Description", breakpoints:"xs sm", style:{"font-size":"11px"}}, + {name:"weight", title:"Score", style:{"font-size":"11px"}}, + {name:"frequency", title:"Frequency", breakpoints:"xs sm", style:{"font-size":"11px"}, sortValue: function (value) { return Number(value).toFixed(2); }}, + {name:"time", title:"Avg. time", breakpoints:"xs sm", style:{"font-size":"11px"}}, + {name:"save", title:"Save", style:{"font-size":"11px"}}, + ], + rows: items[0], + paging: { + enabled: true, + limit: 5, + size: 25 + }, + filtering: { + enabled: true, + position: "left", + connectors: false + }, + sorting: { + enabled: true + }, + components: { + filtering: FooTable.groupFilter + }, + on: { + "ready.ft.table": function () { + if (rspamd.read_only) { + $(".mb-disabled").attr("disabled", true); + } + } + } + }); + }, + server: (checked_server === "All SERVERS") ? "local" : checked_server + }); + $("#symbolsTable") + .off("click", ":button") + .on("click", ":button", function () { + var value = $(this).data("save"); + if (!value) return; + saveSymbols(rspamd, "./savesymbols", "symbolsTable", value); }); - }, - error: function (data) { - rspamd.alertMessage('alert-modal alert-error', data.statusText); - } - }); - $(document).on("click", "#symbolsTable :button", function(event){ - var value = $(this).data('save'); - if (!value) return - saveSymbols(rspamd, "./savesymbols", "symbolsTable", value == 'cluster'); - }); - }; + }; - interface.setup = function(rspamd, tables) { - $('#updateSymbols').on('click', function (e) { - e.preventDefault(); - $.ajax({ - dataType: 'json', - type: 'GET', - jsonp: false, - url: 'symbols', - beforeSend: function (xhr) { - xhr.setRequestHeader('Password', rspamd.getPassword()); - }, - success: function (data) { - var items = process_symbols_data(data)[0]; - ft.symbols.rows.load(items); - }, - error: function (data) { - rspamd.alertMessage('alert-modal alert-error', data.statusText); - } - }); - }); - }; + ui.setup = function (rspamd) { + $("#updateSymbols").on("click", function (e) { + e.preventDefault(); + var checked_server = getSelector("selSrv"); + rspamd.query("symbols", { + success: function (data) { + var items = process_symbols_data(data[0].data)[0]; + ft.symbols.rows.load(items); + }, + server: (checked_server === "All SERVERS") ? "local" : checked_server + }); + }); + }; - return interface; -}); + return ui; + }); diff --git a/interface/js/app/upload.js b/interface/js/app/upload.js index f56ff9105..bbd109639 100644 --- a/interface/js/app/upload.js +++ b/interface/js/app/upload.js @@ -22,193 +22,187 @@ THE SOFTWARE. */ -define(['jquery'], -function($) { - var interface = {} +define(["jquery"], + function ($) { + "use strict"; + var ui = {}; - function cleanTextUpload(source) { - $('#' + source + 'TextSource').val(''); - } - - // @upload text - function uploadText(rspamd, data, source, headers) { - var url; - if (source === 'spam') { - url = 'learnspam'; - } else if (source === 'ham') { - url = 'learnham'; - } else if (source == 'fuzzy') { - url = 'fuzzyadd'; - } else if (source === 'scan') { - url = 'scan'; + function cleanTextUpload(source) { + $("#" + source + "TextSource").val(""); } - $.ajax({ - data: data, - dataType: 'json', - type: 'POST', - url: url, - processData: false, - jsonp: false, - beforeSend: function (xhr) { - xhr.setRequestHeader('Password', rspamd.getPassword()); - $.each(headers, function (name, value) { - xhr.setRequestHeader(name, value); - }); - }, - success: function (data) { - cleanTextUpload(source); - if (data.success) { - rspamd.alertMessage('alert-success', 'Data successfully uploaded'); - } - }, - error: function (xhr, textStatus, errorThrown) { - var errorMsg; - try { - var json = $.parseJSON(xhr.responseText); - errorMsg = $('<a>').text(json.error).html(); - } catch (err) { - errorMsg = $('<a>').text("Error: [" + textStatus + "] " + errorThrown).html(); - } - rspamd.alertMessage('alert-error', errorMsg); + // @upload text + function uploadText(rspamd, data, source, headers) { + var url; + if (source === "spam") { + url = "learnspam"; + } else if (source === "ham") { + url = "learnham"; + } else if (source === "fuzzy") { + url = "fuzzyadd"; + } else if (source === "scan") { + url = "scan"; } - }); - } - // @upload text - function scanText(rspamd, data) { - var url = 'scan'; - var items = []; - $.ajax({ - data: data, - dataType: 'json', - type: 'POST', - url: url, - processData: false, - jsonp: false, - beforeSend: function (xhr) { - xhr.setRequestHeader('Password', rspamd.getPassword()); - }, - success: function (input) { - var data = input; - if (data.action) { - rspamd.alertMessage('alert-success', 'Data successfully scanned'); - var action = ''; - - if (data.action === 'clean' || 'no action') { - action = 'label-success'; - } - else if (data.action === 'rewrite subject' || 'add header' || 'probable spam') { - action = 'label-warning'; - } - else if (data.action === 'spam') { - action = 'label-danger'; + $.ajax({ + data: data, + dataType: "json", + type: "POST", + url: url, + processData: false, + jsonp: false, + beforeSend: function (xhr) { + xhr.setRequestHeader("Password", rspamd.getPassword()); + $.each(headers, function (name, value) { + xhr.setRequestHeader(name, value); + }); + }, + success: function (json) { + cleanTextUpload(source); + if (json.success) { + rspamd.alertMessage("alert-success", "Data successfully uploaded"); } + }, + error: function (xhr, textStatus, errorThrown) { + var errorMsg; - var score = ''; - if (data.score <= data.required_score) { - score = 'label-success'; + try { + var json = $.parseJSON(xhr.responseText); + errorMsg = $("<a>").text(json.error).html(); + } catch (err) { + errorMsg = $("<a>").text("Error: [" + textStatus + "] " + errorThrown).html(); } - else if (data.score >= data.required_score) { - score = 'label-danger'; - } - $('<tbody id="tmpBody"><tr>' + - '<td><span class="label ' + action + '">' + data.action + '</span></td>' + - '<td><span class="label ' + score + '">' + data.score.toFixed(2) + '/' + data.required_score.toFixed(2) + '</span></td>' + - '</tr></tbody>') - .insertAfter('#scanOutput thead'); - var sym_desc = {}; - var nsym = 0; + rspamd.alertMessage("alert-error", errorMsg); + } + }); + } + // @upload text + function scanText(rspamd, data) { + var url = "scan"; + var items = []; + $.ajax({ + data: data, + dataType: "json", + type: "POST", + url: url, + processData: false, + jsonp: false, + beforeSend: function (xhr) { + xhr.setRequestHeader("Password", rspamd.getPassword()); + }, + success: function (json) { + if (json.action) { + rspamd.alertMessage("alert-success", "Data successfully scanned"); + var action = ""; - $.each(data.symbols, function (i, item) { - if (typeof item == 'object') { - var sym_id = "sym_" + nsym; - if (item.description) { - sym_desc[sym_id] = item.description; - } - items.push('<div class="cell-overflow" tabindex="1"><abbr id="' + sym_id + - '">' + item.name + '</abbr>: ' + item.score.toFixed(2) + '</div>'); - nsym++; + if (json.action === "clean" || "no action") { + action = "label-success"; + } else if (json.action === "rewrite subject" || "add header" || "probable spam") { + action = "label-warning"; + } else if (json.action === "spam") { + action = "label-danger"; } - }); - $('<td/>', { - id: 'tmpSymbols', - html: items.join('') - }).appendTo('#scanResult'); - $('#tmpSymbols').insertAfter('#tmpBody td:last').removeAttr('id'); - $('#tmpBody').removeAttr('id'); - $('#scanResult').show(); - // Show tooltips - $.each(sym_desc, function (k, v) { - $('#' + k).tooltip({ - "placement": "bottom", - "title": v + + var score = ""; + if (json.score <= json.required_score) { + score = "label-success"; + } else if (json.score >= json.required_score) { + score = "label-danger"; + } + $("<tbody id=\"tmpBody\"><tr>" + + "<td><span class=\"label " + action + "\">" + json.action + "</span></td>" + + "<td><span class=\"label " + score + "\">" + json.score.toFixed(2) + "/" + json.required_score.toFixed(2) + "</span></td>" + + "</tr></tbody>") + .insertAfter("#scanOutput thead"); + var sym_desc = {}; + var nsym = 0; + + $.each(json.symbols, function (i, item) { + if (typeof item === "object") { + var sym_id = "sym_" + nsym; + if (item.description) { + sym_desc[sym_id] = item.description; + } + items.push("<div class=\"cell-overflow\" tabindex=\"1\"><abbr id=\"" + sym_id + + "\">" + item.name + "</abbr>: " + item.score.toFixed(2) + "</div>"); + nsym++; + } }); - }); - $('html, body').animate({ - scrollTop: $('#scanResult').offset().top - }, 1000); - } else { - rspamd.alertMessage('alert-error', 'Cannot scan data'); - } - }, - error: function (jqXHR, textStatus, errorThrown) { - rspamd.alertMessage('alert-error', 'Cannot upload data: ' + - textStatus + ", " + errorThrown); - }, - statusCode: { - 404: function () { - rspamd.alertMessage('alert-error', 'Cannot upload data, no server found'); - }, - 500: function () { - rspamd.alertMessage('alert-error', 'Cannot tokenize message: no text data'); + $("<td/>", { + id: "tmpSymbols", + html: items.join("") + }).appendTo("#scanResult"); + $("#tmpSymbols").insertAfter("#tmpBody td:last").removeAttr("id"); + $("#tmpBody").removeAttr("id"); + $("#scanResult").show(); + // Show tooltips + $.each(sym_desc, function (k, v) { + $("#" + k).tooltip({ + placement: "bottom", + title: v + }); + }); + $("html, body").animate({ + scrollTop: $("#scanResult").offset().top + }, 1000); + } else { + rspamd.alertMessage("alert-error", "Cannot scan data"); + } }, - 503: function () { - rspamd.alertMessage('alert-error', 'Cannot tokenize message: no text data'); + errorMessage: "Cannot upload data", + statusCode: { + 404: function () { + rspamd.alertMessage("alert-error", "Cannot upload data, no server found"); + }, + 500: function () { + rspamd.alertMessage("alert-error", "Cannot tokenize message: no text data"); + }, + 503: function () { + rspamd.alertMessage("alert-error", "Cannot tokenize message: no text data"); + } } - } - }); - } + }); + } - interface.setup = function(rspamd) { - $('textarea').change(function () { - if ($(this).val().length !== '') { - $(this).closest('form').find('button').removeAttr('disabled').removeClass('disabled'); - } else { - $(this).closest('form').find('button').attr('disabled').addClass('disabled'); - } - }); + ui.setup = function (rspamd) { + $("textarea").change(function () { + if ($(this).val().length !== "") { + $(this).closest("form").find("button").removeAttr("disabled").removeClass("disabled"); + } else { + $(this).closest("form").find("button").attr("disabled").addClass("disabled"); + } + }); - $('#scanClean').on('click', function () { - $('#scanTextSource').val(""); - $('#scanResult').hide(); - $('#scanOutput tbody').remove(); - $('html, body').animate({scrollTop: 0}, 1000); - return false; - }); - // @init upload - $('[data-upload]').on('click', function () { - var source = $(this).data('upload'); - var data; - var headers = {}; - data = $('#' + source + 'TextSource').val(); - if (source == 'fuzzy') { - //To access the proper - headers.flag = $('#fuzzyFlagText').val(); - headers.weight = $('#fuzzyWeightText').val(); - } else { - data = $('#' + source + 'TextSource').val(); - } - if (data.length > 0) { - if (source == 'scan') { - scanText(rspamd, data); + $("#scanClean").on("click", function () { + $("#scanTextSource").val(""); + $("#scanResult").hide(); + $("#scanOutput tbody").remove(); + $("html, body").animate({scrollTop: 0}, 1000); + return false; + }); + // @init upload + $("[data-upload]").on("click", function () { + var source = $(this).data("upload"); + var data; + var headers = {}; + data = $("#" + source + "TextSource").val(); + if (source === "fuzzy") { + // To access the proper + headers.flag = $("#fuzzyFlagText").val(); + headers.weight = $("#fuzzyWeightText").val(); } else { - uploadText(rspamd, data, source, headers); + data = $("#" + source + "TextSource").val(); } - } - return false; - }); - }; + if (data.length > 0) { + if (source === "scan") { + scanText(rspamd, data); + } else { + uploadText(rspamd, data, source, headers); + } + } + return false; + }); + }; - return interface; -}); + return ui; + }); diff --git a/interface/js/lib/require.min.js b/interface/js/lib/require.min.js new file mode 100644 index 000000000..a3ca583b9 --- /dev/null +++ b/interface/js/lib/require.min.js @@ -0,0 +1,5 @@ +/** vim: et:ts=4:sw=4:sts=4 + * @license RequireJS 2.3.5 Copyright jQuery Foundation and other contributors. + * Released under MIT license, https://github.com/requirejs/requirejs/blob/master/LICENSE + */ +var requirejs,require,define;!function(global,setTimeout){function commentReplace(e,t){return t||""}function isFunction(e){return"[object Function]"===ostring.call(e)}function isArray(e){return"[object Array]"===ostring.call(e)}function each(e,t){if(e){var i;for(i=0;i<e.length&&(!e[i]||!t(e[i],i,e));i+=1);}}function eachReverse(e,t){if(e){var i;for(i=e.length-1;i>-1&&(!e[i]||!t(e[i],i,e));i-=1);}}function hasProp(e,t){return hasOwn.call(e,t)}function getOwn(e,t){return hasProp(e,t)&&e[t]}function eachProp(e,t){var i;for(i in e)if(hasProp(e,i)&&t(e[i],i))break}function mixin(e,t,i,r){return t&&eachProp(t,function(t,n){!i&&hasProp(e,n)||(!r||"object"!=typeof t||!t||isArray(t)||isFunction(t)||t instanceof RegExp?e[n]=t:(e[n]||(e[n]={}),mixin(e[n],t,i,r)))}),e}function bind(e,t){return function(){return t.apply(e,arguments)}}function scripts(){return document.getElementsByTagName("script")}function defaultOnError(e){throw e}function getGlobal(e){if(!e)return e;var t=global;return each(e.split("."),function(e){t=t[e]}),t}function makeError(e,t,i,r){var n=new Error(t+"\nhttp://requirejs.org/docs/errors.html#"+e);return n.requireType=e,n.requireModules=r,i&&(n.originalError=i),n}function newContext(e){function t(e){var t,i;for(t=0;t<e.length;t++)if("."===(i=e[t]))e.splice(t,1),t-=1;else if(".."===i){if(0===t||1===t&&".."===e[2]||".."===e[t-1])continue;t>0&&(e.splice(t-1,2),t-=2)}}function i(e,i,r){var n,o,a,s,u,c,d,p,f,l,h=i&&i.split("/"),m=y.map,g=m&&m["*"];if(e&&(c=(e=e.split("/")).length-1,y.nodeIdCompat&&jsSuffixRegExp.test(e[c])&&(e[c]=e[c].replace(jsSuffixRegExp,"")),"."===e[0].charAt(0)&&h&&(e=h.slice(0,h.length-1).concat(e)),t(e),e=e.join("/")),r&&m&&(h||g)){e:for(a=(o=e.split("/")).length;a>0;a-=1){if(u=o.slice(0,a).join("/"),h)for(s=h.length;s>0;s-=1)if((n=getOwn(m,h.slice(0,s).join("/")))&&(n=getOwn(n,u))){d=n,p=a;break e}!f&&g&&getOwn(g,u)&&(f=getOwn(g,u),l=a)}!d&&f&&(d=f,p=l),d&&(o.splice(0,p,d),e=o.join("/"))}return getOwn(y.pkgs,e)||e}function r(e){isBrowser&&each(scripts(),function(t){if(t.getAttribute("data-requiremodule")===e&&t.getAttribute("data-requirecontext")===q.contextName)return t.parentNode.removeChild(t),!0})}function n(e){var t=getOwn(y.paths,e);if(t&&isArray(t)&&t.length>1)return t.shift(),q.require.undef(e),q.makeRequire(null,{skipMap:!0})([e]),!0}function o(e){var t,i=e?e.indexOf("!"):-1;return i>-1&&(t=e.substring(0,i),e=e.substring(i+1,e.length)),[t,e]}function a(e,t,r,n){var a,s,u,c,d=null,p=t?t.name:null,f=e,l=!0,h="";return e||(l=!1,e="_@r"+(T+=1)),c=o(e),d=c[0],e=c[1],d&&(d=i(d,p,n),s=getOwn(j,d)),e&&(d?h=r?e:s&&s.normalize?s.normalize(e,function(e){return i(e,p,n)}):-1===e.indexOf("!")?i(e,p,n):e:(d=(c=o(h=i(e,p,n)))[0],h=c[1],r=!0,a=q.nameToUrl(h))),u=!d||s||r?"":"_unnormalized"+(A+=1),{prefix:d,name:h,parentMap:t,unnormalized:!!u,url:a,originalName:f,isDefine:l,id:(d?d+"!"+h:h)+u}}function s(e){var t=e.id,i=getOwn(S,t);return i||(i=S[t]=new q.Module(e)),i}function u(e,t,i){var r=e.id,n=getOwn(S,r);!hasProp(j,r)||n&&!n.defineEmitComplete?(n=s(e)).error&&"error"===t?i(n.error):n.on(t,i):"defined"===t&&i(j[r])}function c(e,t){var i=e.requireModules,r=!1;t?t(e):(each(i,function(t){var i=getOwn(S,t);i&&(i.error=e,i.events.error&&(r=!0,i.emit("error",e)))}),r||req.onError(e))}function d(){globalDefQueue.length&&(each(globalDefQueue,function(e){var t=e[0];"string"==typeof t&&(q.defQueueMap[t]=!0),O.push(e)}),globalDefQueue=[])}function p(e){delete S[e],delete k[e]}function f(e,t,i){var r=e.map.id;e.error?e.emit("error",e.error):(t[r]=!0,each(e.depMaps,function(r,n){var o=r.id,a=getOwn(S,o);!a||e.depMatched[n]||i[o]||(getOwn(t,o)?(e.defineDep(n,j[o]),e.check()):f(a,t,i))}),i[r]=!0)}function l(){var e,t,i=1e3*y.waitSeconds,o=i&&q.startTime+i<(new Date).getTime(),a=[],s=[],u=!1,d=!0;if(!x){if(x=!0,eachProp(k,function(e){var i=e.map,c=i.id;if(e.enabled&&(i.isDefine||s.push(e),!e.error))if(!e.inited&&o)n(c)?(t=!0,u=!0):(a.push(c),r(c));else if(!e.inited&&e.fetched&&i.isDefine&&(u=!0,!i.prefix))return d=!1}),o&&a.length)return e=makeError("timeout","Load timeout for modules: "+a,null,a),e.contextName=q.contextName,c(e);d&&each(s,function(e){f(e,{},{})}),o&&!t||!u||!isBrowser&&!isWebWorker||w||(w=setTimeout(function(){w=0,l()},50)),x=!1}}function h(e){hasProp(j,e[0])||s(a(e[0],null,!0)).init(e[1],e[2])}function m(e,t,i,r){e.detachEvent&&!isOpera?r&&e.detachEvent(r,t):e.removeEventListener(i,t,!1)}function g(e){var t=e.currentTarget||e.srcElement;return m(t,q.onScriptLoad,"load","onreadystatechange"),m(t,q.onScriptError,"error"),{node:t,id:t&&t.getAttribute("data-requiremodule")}}function v(){var e;for(d();O.length;){if(null===(e=O.shift())[0])return c(makeError("mismatch","Mismatched anonymous define() module: "+e[e.length-1]));h(e)}q.defQueueMap={}}var x,b,q,E,w,y={waitSeconds:7,baseUrl:"./",paths:{},bundles:{},pkgs:{},shim:{},config:{}},S={},k={},M={},O=[],j={},P={},R={},T=1,A=1;return E={require:function(e){return e.require?e.require:e.require=q.makeRequire(e.map)},exports:function(e){if(e.usingExports=!0,e.map.isDefine)return e.exports?j[e.map.id]=e.exports:e.exports=j[e.map.id]={}},module:function(e){return e.module?e.module:e.module={id:e.map.id,uri:e.map.url,config:function(){return getOwn(y.config,e.map.id)||{}},exports:e.exports||(e.exports={})}}},b=function(e){this.events=getOwn(M,e.id)||{},this.map=e,this.shim=getOwn(y.shim,e.id),this.depExports=[],this.depMaps=[],this.depMatched=[],this.pluginMaps={},this.depCount=0},b.prototype={init:function(e,t,i,r){r=r||{},this.inited||(this.factory=t,i?this.on("error",i):this.events.error&&(i=bind(this,function(e){this.emit("error",e)})),this.depMaps=e&&e.slice(0),this.errback=i,this.inited=!0,this.ignore=r.ignore,r.enabled||this.enabled?this.enable():this.check())},defineDep:function(e,t){this.depMatched[e]||(this.depMatched[e]=!0,this.depCount-=1,this.depExports[e]=t)},fetch:function(){if(!this.fetched){this.fetched=!0,q.startTime=(new Date).getTime();var e=this.map;if(!this.shim)return e.prefix?this.callPlugin():this.load();q.makeRequire(this.map,{enableBuildCallback:!0})(this.shim.deps||[],bind(this,function(){return e.prefix?this.callPlugin():this.load()}))}},load:function(){var e=this.map.url;P[e]||(P[e]=!0,q.load(this.map.id,e))},check:function(){if(this.enabled&&!this.enabling){var e,t,i=this.map.id,r=this.depExports,n=this.exports,o=this.factory;if(this.inited){if(this.error)this.emit("error",this.error);else if(!this.defining){if(this.defining=!0,this.depCount<1&&!this.defined){if(isFunction(o)){if(this.events.error&&this.map.isDefine||req.onError!==defaultOnError)try{n=q.execCb(i,o,r,n)}catch(t){e=t}else n=q.execCb(i,o,r,n);if(this.map.isDefine&&void 0===n&&((t=this.module)?n=t.exports:this.usingExports&&(n=this.exports)),e)return e.requireMap=this.map,e.requireModules=this.map.isDefine?[this.map.id]:null,e.requireType=this.map.isDefine?"define":"require",c(this.error=e)}else n=o;if(this.exports=n,this.map.isDefine&&!this.ignore&&(j[i]=n,req.onResourceLoad)){var a=[];each(this.depMaps,function(e){a.push(e.normalizedMap||e)}),req.onResourceLoad(q,this.map,a)}p(i),this.defined=!0}this.defining=!1,this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0)}}else hasProp(q.defQueueMap,i)||this.fetch()}},callPlugin:function(){var e=this.map,t=e.id,r=a(e.prefix);this.depMaps.push(r),u(r,"defined",bind(this,function(r){var n,o,d,f=getOwn(R,this.map.id),l=this.map.name,h=this.map.parentMap?this.map.parentMap.name:null,m=q.makeRequire(e.parentMap,{enableBuildCallback:!0});return this.map.unnormalized?(r.normalize&&(l=r.normalize(l,function(e){return i(e,h,!0)})||""),o=a(e.prefix+"!"+l,this.map.parentMap,!0),u(o,"defined",bind(this,function(e){this.map.normalizedMap=o,this.init([],function(){return e},null,{enabled:!0,ignore:!0})})),void((d=getOwn(S,o.id))&&(this.depMaps.push(o),this.events.error&&d.on("error",bind(this,function(e){this.emit("error",e)})),d.enable()))):f?(this.map.url=q.nameToUrl(f),void this.load()):((n=bind(this,function(e){this.init([],function(){return e},null,{enabled:!0})})).error=bind(this,function(e){this.inited=!0,this.error=e,e.requireModules=[t],eachProp(S,function(e){0===e.map.id.indexOf(t+"_unnormalized")&&p(e.map.id)}),c(e)}),n.fromText=bind(this,function(i,r){var o=e.name,u=a(o),d=useInteractive;r&&(i=r),d&&(useInteractive=!1),s(u),hasProp(y.config,t)&&(y.config[o]=y.config[t]);try{req.exec(i)}catch(e){return c(makeError("fromtexteval","fromText eval for "+t+" failed: "+e,e,[t]))}d&&(useInteractive=!0),this.depMaps.push(u),q.completeLoad(o),m([o],n)}),void r.load(e.name,m,n,y))})),q.enable(r,this),this.pluginMaps[r.id]=r},enable:function(){k[this.map.id]=this,this.enabled=!0,this.enabling=!0,each(this.depMaps,bind(this,function(e,t){var i,r,n;if("string"==typeof e){if(e=a(e,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap),this.depMaps[t]=e,n=getOwn(E,e.id))return void(this.depExports[t]=n(this));this.depCount+=1,u(e,"defined",bind(this,function(e){this.undefed||(this.defineDep(t,e),this.check())})),this.errback?u(e,"error",bind(this,this.errback)):this.events.error&&u(e,"error",bind(this,function(e){this.emit("error",e)}))}i=e.id,r=S[i],hasProp(E,i)||!r||r.enabled||q.enable(e,this)})),eachProp(this.pluginMaps,bind(this,function(e){var t=getOwn(S,e.id);t&&!t.enabled&&q.enable(e,this)})),this.enabling=!1,this.check()},on:function(e,t){var i=this.events[e];i||(i=this.events[e]=[]),i.push(t)},emit:function(e,t){each(this.events[e],function(e){e(t)}),"error"===e&&delete this.events[e]}},q={config:y,contextName:e,registry:S,defined:j,urlFetched:P,defQueue:O,defQueueMap:{},Module:b,makeModuleMap:a,nextTick:req.nextTick,onError:c,configure:function(e){if(e.baseUrl&&"/"!==e.baseUrl.charAt(e.baseUrl.length-1)&&(e.baseUrl+="/"),"string"==typeof e.urlArgs){var t=e.urlArgs;e.urlArgs=function(e,i){return(-1===i.indexOf("?")?"?":"&")+t}}var i=y.shim,r={paths:!0,bundles:!0,config:!0,map:!0};eachProp(e,function(e,t){r[t]?(y[t]||(y[t]={}),mixin(y[t],e,!0,!0)):y[t]=e}),e.bundles&&eachProp(e.bundles,function(e,t){each(e,function(e){e!==t&&(R[e]=t)})}),e.shim&&(eachProp(e.shim,function(e,t){isArray(e)&&(e={deps:e}),!e.exports&&!e.init||e.exportsFn||(e.exportsFn=q.makeShimExports(e)),i[t]=e}),y.shim=i),e.packages&&each(e.packages,function(e){var t;t=(e="string"==typeof e?{name:e}:e).name,e.location&&(y.paths[t]=e.location),y.pkgs[t]=e.name+"/"+(e.main||"main").replace(currDirRegExp,"").replace(jsSuffixRegExp,"")}),eachProp(S,function(e,t){e.inited||e.map.unnormalized||(e.map=a(t,null,!0))}),(e.deps||e.callback)&&q.require(e.deps||[],e.callback)},makeShimExports:function(e){return function(){var t;return e.init&&(t=e.init.apply(global,arguments)),t||e.exports&&getGlobal(e.exports)}},makeRequire:function(t,n){function o(i,r,u){var d,p,f;return n.enableBuildCallback&&r&&isFunction(r)&&(r.__requireJsBuild=!0),"string"==typeof i?isFunction(r)?c(makeError("requireargs","Invalid require call"),u):t&&hasProp(E,i)?E[i](S[t.id]):req.get?req.get(q,i,t,o):(p=a(i,t,!1,!0),d=p.id,hasProp(j,d)?j[d]:c(makeError("notloaded",'Module name "'+d+'" has not been loaded yet for context: '+e+(t?"":". Use require([])")))):(v(),q.nextTick(function(){v(),(f=s(a(null,t))).skipMap=n.skipMap,f.init(i,r,u,{enabled:!0}),l()}),o)}return n=n||{},mixin(o,{isBrowser:isBrowser,toUrl:function(e){var r,n=e.lastIndexOf("."),o=e.split("/")[0],a="."===o||".."===o;return-1!==n&&(!a||n>1)&&(r=e.substring(n,e.length),e=e.substring(0,n)),q.nameToUrl(i(e,t&&t.id,!0),r,!0)},defined:function(e){return hasProp(j,a(e,t,!1,!0).id)},specified:function(e){return e=a(e,t,!1,!0).id,hasProp(j,e)||hasProp(S,e)}}),t||(o.undef=function(e){d();var i=a(e,t,!0),n=getOwn(S,e);n.undefed=!0,r(e),delete j[e],delete P[i.url],delete M[e],eachReverse(O,function(t,i){t[0]===e&&O.splice(i,1)}),delete q.defQueueMap[e],n&&(n.events.defined&&(M[e]=n.events),p(e))}),o},enable:function(e){getOwn(S,e.id)&&s(e).enable()},completeLoad:function(e){var t,i,r,o=getOwn(y.shim,e)||{},a=o.exports;for(d();O.length;){if(null===(i=O.shift())[0]){if(i[0]=e,t)break;t=!0}else i[0]===e&&(t=!0);h(i)}if(q.defQueueMap={},r=getOwn(S,e),!t&&!hasProp(j,e)&&r&&!r.inited){if(!(!y.enforceDefine||a&&getGlobal(a)))return n(e)?void 0:c(makeError("nodefine","No define call for "+e,null,[e]));h([e,o.deps||[],o.exportsFn])}l()},nameToUrl:function(e,t,i){var r,n,o,a,s,u,c,d=getOwn(y.pkgs,e);if(d&&(e=d),c=getOwn(R,e))return q.nameToUrl(c,t,i);if(req.jsExtRegExp.test(e))s=e+(t||"");else{for(r=y.paths,o=(n=e.split("/")).length;o>0;o-=1)if(a=n.slice(0,o).join("/"),u=getOwn(r,a)){isArray(u)&&(u=u[0]),n.splice(0,o,u);break}s=n.join("/"),s=("/"===(s+=t||(/^data\:|^blob\:|\?/.test(s)||i?"":".js")).charAt(0)||s.match(/^[\w\+\.\-]+:/)?"":y.baseUrl)+s}return y.urlArgs&&!/^blob\:/.test(s)?s+y.urlArgs(e,s):s},load:function(e,t){req.load(q,e,t)},execCb:function(e,t,i,r){return t.apply(r,i)},onScriptLoad:function(e){if("load"===e.type||readyRegExp.test((e.currentTarget||e.srcElement).readyState)){interactiveScript=null;var t=g(e);q.completeLoad(t.id)}},onScriptError:function(e){var t=g(e);if(!n(t.id)){var i=[];return eachProp(S,function(e,r){0!==r.indexOf("_@r")&&each(e.depMaps,function(e){if(e.id===t.id)return i.push(r),!0})}),c(makeError("scripterror",'Script error for "'+t.id+(i.length?'", needed by: '+i.join(", "):'"'),e,[t.id]))}}},q.require=q.makeRequire(),q}function getInteractiveScript(){return interactiveScript&&"interactive"===interactiveScript.readyState?interactiveScript:(eachReverse(scripts(),function(e){if("interactive"===e.readyState)return interactiveScript=e}),interactiveScript)}var req,s,head,baseElement,dataMain,src,interactiveScript,currentlyAddingScript,mainScript,subPath,version="2.3.5",commentRegExp=/\/\*[\s\S]*?\*\/|([^:"'=]|^)\/\/.*$/gm,cjsRequireRegExp=/[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,jsSuffixRegExp=/\.js$/,currDirRegExp=/^\.\//,op=Object.prototype,ostring=op.toString,hasOwn=op.hasOwnProperty,isBrowser=!("undefined"==typeof window||"undefined"==typeof navigator||!window.document),isWebWorker=!isBrowser&&"undefined"!=typeof importScripts,readyRegExp=isBrowser&&"PLAYSTATION 3"===navigator.platform?/^complete$/:/^(complete|loaded)$/,defContextName="_",isOpera="undefined"!=typeof opera&&"[object Opera]"===opera.toString(),contexts={},cfg={},globalDefQueue=[],useInteractive=!1;if(void 0===define){if(void 0!==requirejs){if(isFunction(requirejs))return;cfg=requirejs,requirejs=void 0}void 0===require||isFunction(require)||(cfg=require,require=void 0),req=requirejs=function(e,t,i,r){var n,o,a=defContextName;return isArray(e)||"string"==typeof e||(o=e,isArray(t)?(e=t,t=i,i=r):e=[]),o&&o.context&&(a=o.context),(n=getOwn(contexts,a))||(n=contexts[a]=req.s.newContext(a)),o&&n.configure(o),n.require(e,t,i)},req.config=function(e){return req(e)},req.nextTick=void 0!==setTimeout?function(e){setTimeout(e,4)}:function(e){e()},require||(require=req),req.version=version,req.jsExtRegExp=/^\/|:|\?|\.js$/,req.isBrowser=isBrowser,s=req.s={contexts:contexts,newContext:newContext},req({}),each(["toUrl","undef","defined","specified"],function(e){req[e]=function(){var t=contexts[defContextName];return t.require[e].apply(t,arguments)}}),isBrowser&&(head=s.head=document.getElementsByTagName("head")[0],(baseElement=document.getElementsByTagName("base")[0])&&(head=s.head=baseElement.parentNode)),req.onError=defaultOnError,req.createNode=function(e,t,i){var r=e.xhtml?document.createElementNS("http://www.w3.org/1999/xhtml","html:script"):document.createElement("script");return r.type=e.scriptType||"text/javascript",r.charset="utf-8",r.async=!0,r},req.load=function(e,t,i){var r,n=e&&e.config||{};if(isBrowser)return(r=req.createNode(n,t,i)).setAttribute("data-requirecontext",e.contextName),r.setAttribute("data-requiremodule",t),!r.attachEvent||r.attachEvent.toString&&r.attachEvent.toString().indexOf("[native code")<0||isOpera?(r.addEventListener("load",e.onScriptLoad,!1),r.addEventListener("error",e.onScriptError,!1)):(useInteractive=!0,r.attachEvent("onreadystatechange",e.onScriptLoad)),r.src=i,n.onNodeCreated&&n.onNodeCreated(r,n,t,i),currentlyAddingScript=r,baseElement?head.insertBefore(r,baseElement):head.appendChild(r),currentlyAddingScript=null,r;if(isWebWorker)try{setTimeout(function(){},0),importScripts(i),e.completeLoad(t)}catch(r){e.onError(makeError("importscripts","importScripts failed for "+t+" at "+i,r,[t]))}},isBrowser&&!cfg.skipDataMain&&eachReverse(scripts(),function(e){if(head||(head=e.parentNode),dataMain=e.getAttribute("data-main"))return mainScript=dataMain,cfg.baseUrl||-1!==mainScript.indexOf("!")||(src=mainScript.split("/"),mainScript=src.pop(),subPath=src.length?src.join("/")+"/":"./",cfg.baseUrl=subPath),mainScript=mainScript.replace(jsSuffixRegExp,""),req.jsExtRegExp.test(mainScript)&&(mainScript=dataMain),cfg.deps=cfg.deps?cfg.deps.concat(mainScript):[mainScript],!0}),define=function(e,t,i){var r,n;"string"!=typeof e&&(i=t,t=e,e=null),isArray(t)||(i=t,t=null),!t&&isFunction(i)&&(t=[],i.length&&(i.toString().replace(commentRegExp,commentReplace).replace(cjsRequireRegExp,function(e,i){t.push(i)}),t=(1===i.length?["require"]:["require","exports","module"]).concat(t))),useInteractive&&(r=currentlyAddingScript||getInteractiveScript())&&(e||(e=r.getAttribute("data-requiremodule")),n=contexts[r.getAttribute("data-requirecontext")]),n?(n.defQueue.push([e,t,i]),n.defQueueMap[e]=!0):globalDefQueue.push([e,t,i])},define.amd={jQuery:!0},req.exec=function(text){return eval(text)},req(cfg)}}(this,"undefined"==typeof setTimeout?void 0:setTimeout);
\ No newline at end of file diff --git a/interface/js/main.js b/interface/js/main.js index 76ceec468..bdc89dd5f 100644 --- a/interface/js/main.js +++ b/interface/js/main.js @@ -1,23 +1,24 @@ +/* global d3:false, require:false, requirejs:false */ // eslint-disable-line no-unused-vars + requirejs.config({ - baseUrl: 'js/lib', + baseUrl: "js/lib", paths: { - app: '../app', - jquery: 'jquery-3.3.1.min', - visibility: 'visibility.min', - humanize: 'humanize.min', - bootstrap: 'bootstrap.min', - d3: 'd3.min', - d3evolution: 'd3evolution.min', - d3pie: 'd3pie.min', - footable: 'footable.min', - bootstrap: 'bootstrap.min', + app: "../app", + jquery: "jquery-3.3.1.min", + visibility: "visibility.min", + humanize: "humanize.min", + bootstrap: "bootstrap.min", + d3: "d3.min", + d3evolution: "d3evolution.min", + d3pie: "d3pie.min", + footable: "footable.min", }, shim: { - d3: {exports: 'd3'}, - bootstrap: {exports: 'bootstrap', deps: ['jquery']}, - d3pie: {exports: 'd3pie', deps: ['d3.global', 'jquery']}, - d3evolution: {exports: 'D3Evolution', deps: ['d3', 'd3pie', 'jquery']}, - footable: {deps: ['bootstrap', 'jquery']} + d3: {exports: "d3"}, + bootstrap: {exports: "bootstrap", deps: ["jquery"]}, + d3pie: {exports: "d3pie", deps: ["d3.global", "jquery"]}, + d3evolution: {exports: "D3Evolution", deps: ["d3", "d3pie", "jquery"]}, + footable: {deps: ["bootstrap", "jquery"]} } }); @@ -26,18 +27,19 @@ document.title = window.location.hostname + (window.location.pathname !== "/" ? window.location.pathname : "") + " - Rspamd Web Interface"; -define("d3.global", ["d3"], function(_) { - d3 = _; +define("d3.global", ["d3"], function (_) { // eslint-disable-line strict + d3 = _; // eslint-disable-line no-global-assign }); // Load main UI -require(['domReady'], -function(domReady) { - domReady(function () { - require(['jquery', 'd3', 'app/rspamd'], - function ($, d3, rspamd) { - rspamd.setup(); - rspamd.connect(); - }); +require(["domReady"], + function (domReady) { + "use strict"; + domReady(function () { + require(["jquery", "d3", "app/rspamd"], + function ($, d3, rspamd) { + rspamd.setup(); + rspamd.connect(); + }); + }); }); -}); diff --git a/interface/js/require.js b/interface/js/require.js deleted file mode 100644 index 3eaa2a72f..000000000 --- a/interface/js/require.js +++ /dev/null @@ -1,5 +0,0 @@ -/** vim: et:ts=4:sw=4:sts=4 - * @license RequireJS 2.3.2 Copyright jQuery Foundation and other contributors. - * Released under MIT license, https://github.com/requirejs/requirejs/blob/master/LICENSE - */ -var requirejs,require,define;!function(global,setTimeout){function commentReplace(e,t){return t||""}function isFunction(e){return"[object Function]"===ostring.call(e)}function isArray(e){return"[object Array]"===ostring.call(e)}function each(e,t){if(e){var i;for(i=0;i<e.length&&(!e[i]||!t(e[i],i,e));i+=1);}}function eachReverse(e,t){if(e){var i;for(i=e.length-1;i>-1&&(!e[i]||!t(e[i],i,e));i-=1);}}function hasProp(e,t){return hasOwn.call(e,t)}function getOwn(e,t){return hasProp(e,t)&&e[t]}function eachProp(e,t){var i;for(i in e)if(hasProp(e,i)&&t(e[i],i))break}function mixin(e,t,i,r){return t&&eachProp(t,function(t,n){!i&&hasProp(e,n)||(!r||"object"!=typeof t||!t||isArray(t)||isFunction(t)||t instanceof RegExp?e[n]=t:(e[n]||(e[n]={}),mixin(e[n],t,i,r)))}),e}function bind(e,t){return function(){return t.apply(e,arguments)}}function scripts(){return document.getElementsByTagName("script")}function defaultOnError(e){throw e}function getGlobal(e){if(!e)return e;var t=global;return each(e.split("."),function(e){t=t[e]}),t}function makeError(e,t,i,r){var n=new Error(t+"\nhttp://requirejs.org/docs/errors.html#"+e);return n.requireType=e,n.requireModules=r,i&&(n.originalError=i),n}function newContext(e){function t(e){var t,i;for(t=0;t<e.length;t++)if(i=e[t],"."===i)e.splice(t,1),t-=1;else if(".."===i){if(0===t||1===t&&".."===e[2]||".."===e[t-1])continue;t>0&&(e.splice(t-1,2),t-=2)}}function i(e,i,r){var n,o,a,s,u,c,d,p,f,l,h,m,g=i&&i.split("/"),v=y.map,x=v&&v["*"];if(e&&(e=e.split("/"),d=e.length-1,y.nodeIdCompat&&jsSuffixRegExp.test(e[d])&&(e[d]=e[d].replace(jsSuffixRegExp,"")),"."===e[0].charAt(0)&&g&&(m=g.slice(0,g.length-1),e=m.concat(e)),t(e),e=e.join("/")),r&&v&&(g||x)){a=e.split("/");e:for(s=a.length;s>0;s-=1){if(c=a.slice(0,s).join("/"),g)for(u=g.length;u>0;u-=1)if(o=getOwn(v,g.slice(0,u).join("/")),o&&(o=getOwn(o,c))){p=o,f=s;break e}!l&&x&&getOwn(x,c)&&(l=getOwn(x,c),h=s)}!p&&l&&(p=l,f=h),p&&(a.splice(0,f,p),e=a.join("/"))}return n=getOwn(y.pkgs,e),n?n:e}function r(e){isBrowser&&each(scripts(),function(t){if(t.getAttribute("data-requiremodule")===e&&t.getAttribute("data-requirecontext")===q.contextName)return t.parentNode.removeChild(t),!0})}function n(e){var t=getOwn(y.paths,e);if(t&&isArray(t)&&t.length>1)return t.shift(),q.require.undef(e),q.makeRequire(null,{skipMap:!0})([e]),!0}function o(e){var t,i=e?e.indexOf("!"):-1;return i>-1&&(t=e.substring(0,i),e=e.substring(i+1,e.length)),[t,e]}function a(e,t,r,n){var a,s,u,c,d=null,p=t?t.name:null,f=e,l=!0,h="";return e||(l=!1,e="_@r"+(T+=1)),c=o(e),d=c[0],e=c[1],d&&(d=i(d,p,n),s=getOwn(j,d)),e&&(d?h=s&&s.normalize?s.normalize(e,function(e){return i(e,p,n)}):e.indexOf("!")===-1?i(e,p,n):e:(h=i(e,p,n),c=o(h),d=c[0],h=c[1],r=!0,a=q.nameToUrl(h))),u=!d||s||r?"":"_unnormalized"+(A+=1),{prefix:d,name:h,parentMap:t,unnormalized:!!u,url:a,originalName:f,isDefine:l,id:(d?d+"!"+h:h)+u}}function s(e){var t=e.id,i=getOwn(S,t);return i||(i=S[t]=new q.Module(e)),i}function u(e,t,i){var r=e.id,n=getOwn(S,r);!hasProp(j,r)||n&&!n.defineEmitComplete?(n=s(e),n.error&&"error"===t?i(n.error):n.on(t,i)):"defined"===t&&i(j[r])}function c(e,t){var i=e.requireModules,r=!1;t?t(e):(each(i,function(t){var i=getOwn(S,t);i&&(i.error=e,i.events.error&&(r=!0,i.emit("error",e)))}),r||req.onError(e))}function d(){globalDefQueue.length&&(each(globalDefQueue,function(e){var t=e[0];"string"==typeof t&&(q.defQueueMap[t]=!0),O.push(e)}),globalDefQueue=[])}function p(e){delete S[e],delete k[e]}function f(e,t,i){var r=e.map.id;e.error?e.emit("error",e.error):(t[r]=!0,each(e.depMaps,function(r,n){var o=r.id,a=getOwn(S,o);!a||e.depMatched[n]||i[o]||(getOwn(t,o)?(e.defineDep(n,j[o]),e.check()):f(a,t,i))}),i[r]=!0)}function l(){var e,t,i=1e3*y.waitSeconds,o=i&&q.startTime+i<(new Date).getTime(),a=[],s=[],u=!1,d=!0;if(!x){if(x=!0,eachProp(k,function(e){var i=e.map,c=i.id;if(e.enabled&&(i.isDefine||s.push(e),!e.error))if(!e.inited&&o)n(c)?(t=!0,u=!0):(a.push(c),r(c));else if(!e.inited&&e.fetched&&i.isDefine&&(u=!0,!i.prefix))return d=!1}),o&&a.length)return e=makeError("timeout","Load timeout for modules: "+a,null,a),e.contextName=q.contextName,c(e);d&&each(s,function(e){f(e,{},{})}),o&&!t||!u||!isBrowser&&!isWebWorker||w||(w=setTimeout(function(){w=0,l()},50)),x=!1}}function h(e){hasProp(j,e[0])||s(a(e[0],null,!0)).init(e[1],e[2])}function m(e,t,i,r){e.detachEvent&&!isOpera?r&&e.detachEvent(r,t):e.removeEventListener(i,t,!1)}function g(e){var t=e.currentTarget||e.srcElement;return m(t,q.onScriptLoad,"load","onreadystatechange"),m(t,q.onScriptError,"error"),{node:t,id:t&&t.getAttribute("data-requiremodule")}}function v(){var e;for(d();O.length;){if(e=O.shift(),null===e[0])return c(makeError("mismatch","Mismatched anonymous define() module: "+e[e.length-1]));h(e)}q.defQueueMap={}}var x,b,q,E,w,y={waitSeconds:7,baseUrl:"./",paths:{},bundles:{},pkgs:{},shim:{},config:{}},S={},k={},M={},O=[],j={},P={},R={},T=1,A=1;return E={require:function(e){return e.require?e.require:e.require=q.makeRequire(e.map)},exports:function(e){if(e.usingExports=!0,e.map.isDefine)return e.exports?j[e.map.id]=e.exports:e.exports=j[e.map.id]={}},module:function(e){return e.module?e.module:e.module={id:e.map.id,uri:e.map.url,config:function(){return getOwn(y.config,e.map.id)||{}},exports:e.exports||(e.exports={})}}},b=function(e){this.events=getOwn(M,e.id)||{},this.map=e,this.shim=getOwn(y.shim,e.id),this.depExports=[],this.depMaps=[],this.depMatched=[],this.pluginMaps={},this.depCount=0},b.prototype={init:function(e,t,i,r){r=r||{},this.inited||(this.factory=t,i?this.on("error",i):this.events.error&&(i=bind(this,function(e){this.emit("error",e)})),this.depMaps=e&&e.slice(0),this.errback=i,this.inited=!0,this.ignore=r.ignore,r.enabled||this.enabled?this.enable():this.check())},defineDep:function(e,t){this.depMatched[e]||(this.depMatched[e]=!0,this.depCount-=1,this.depExports[e]=t)},fetch:function(){if(!this.fetched){this.fetched=!0,q.startTime=(new Date).getTime();var e=this.map;return this.shim?void q.makeRequire(this.map,{enableBuildCallback:!0})(this.shim.deps||[],bind(this,function(){return e.prefix?this.callPlugin():this.load()})):e.prefix?this.callPlugin():this.load()}},load:function(){var e=this.map.url;P[e]||(P[e]=!0,q.load(this.map.id,e))},check:function(){if(this.enabled&&!this.enabling){var e,t,i=this.map.id,r=this.depExports,n=this.exports,o=this.factory;if(this.inited){if(this.error)this.emit("error",this.error);else if(!this.defining){if(this.defining=!0,this.depCount<1&&!this.defined){if(isFunction(o)){if(this.events.error&&this.map.isDefine||req.onError!==defaultOnError)try{n=q.execCb(i,o,r,n)}catch(t){e=t}else n=q.execCb(i,o,r,n);if(this.map.isDefine&&void 0===n&&(t=this.module,t?n=t.exports:this.usingExports&&(n=this.exports)),e)return e.requireMap=this.map,e.requireModules=this.map.isDefine?[this.map.id]:null,e.requireType=this.map.isDefine?"define":"require",c(this.error=e)}else n=o;if(this.exports=n,this.map.isDefine&&!this.ignore&&(j[i]=n,req.onResourceLoad)){var a=[];each(this.depMaps,function(e){a.push(e.normalizedMap||e)}),req.onResourceLoad(q,this.map,a)}p(i),this.defined=!0}this.defining=!1,this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0)}}else hasProp(q.defQueueMap,i)||this.fetch()}},callPlugin:function(){var e=this.map,t=e.id,r=a(e.prefix);this.depMaps.push(r),u(r,"defined",bind(this,function(r){var n,o,d,f=getOwn(R,this.map.id),l=this.map.name,h=this.map.parentMap?this.map.parentMap.name:null,m=q.makeRequire(e.parentMap,{enableBuildCallback:!0});return this.map.unnormalized?(r.normalize&&(l=r.normalize(l,function(e){return i(e,h,!0)})||""),o=a(e.prefix+"!"+l,this.map.parentMap),u(o,"defined",bind(this,function(e){this.map.normalizedMap=o,this.init([],function(){return e},null,{enabled:!0,ignore:!0})})),d=getOwn(S,o.id),void(d&&(this.depMaps.push(o),this.events.error&&d.on("error",bind(this,function(e){this.emit("error",e)})),d.enable()))):f?(this.map.url=q.nameToUrl(f),void this.load()):(n=bind(this,function(e){this.init([],function(){return e},null,{enabled:!0})}),n.error=bind(this,function(e){this.inited=!0,this.error=e,e.requireModules=[t],eachProp(S,function(e){0===e.map.id.indexOf(t+"_unnormalized")&&p(e.map.id)}),c(e)}),n.fromText=bind(this,function(i,r){var o=e.name,u=a(o),d=useInteractive;r&&(i=r),d&&(useInteractive=!1),s(u),hasProp(y.config,t)&&(y.config[o]=y.config[t]);try{req.exec(i)}catch(e){return c(makeError("fromtexteval","fromText eval for "+t+" failed: "+e,e,[t]))}d&&(useInteractive=!0),this.depMaps.push(u),q.completeLoad(o),m([o],n)}),void r.load(e.name,m,n,y))})),q.enable(r,this),this.pluginMaps[r.id]=r},enable:function(){k[this.map.id]=this,this.enabled=!0,this.enabling=!0,each(this.depMaps,bind(this,function(e,t){var i,r,n;if("string"==typeof e){if(e=a(e,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap),this.depMaps[t]=e,n=getOwn(E,e.id))return void(this.depExports[t]=n(this));this.depCount+=1,u(e,"defined",bind(this,function(e){this.undefed||(this.defineDep(t,e),this.check())})),this.errback?u(e,"error",bind(this,this.errback)):this.events.error&&u(e,"error",bind(this,function(e){this.emit("error",e)}))}i=e.id,r=S[i],hasProp(E,i)||!r||r.enabled||q.enable(e,this)})),eachProp(this.pluginMaps,bind(this,function(e){var t=getOwn(S,e.id);t&&!t.enabled&&q.enable(e,this)})),this.enabling=!1,this.check()},on:function(e,t){var i=this.events[e];i||(i=this.events[e]=[]),i.push(t)},emit:function(e,t){each(this.events[e],function(e){e(t)}),"error"===e&&delete this.events[e]}},q={config:y,contextName:e,registry:S,defined:j,urlFetched:P,defQueue:O,defQueueMap:{},Module:b,makeModuleMap:a,nextTick:req.nextTick,onError:c,configure:function(e){if(e.baseUrl&&"/"!==e.baseUrl.charAt(e.baseUrl.length-1)&&(e.baseUrl+="/"),"string"==typeof e.urlArgs){var t=e.urlArgs;e.urlArgs=function(e,i){return(i.indexOf("?")===-1?"?":"&")+t}}var i=y.shim,r={paths:!0,bundles:!0,config:!0,map:!0};eachProp(e,function(e,t){r[t]?(y[t]||(y[t]={}),mixin(y[t],e,!0,!0)):y[t]=e}),e.bundles&&eachProp(e.bundles,function(e,t){each(e,function(e){e!==t&&(R[e]=t)})}),e.shim&&(eachProp(e.shim,function(e,t){isArray(e)&&(e={deps:e}),!e.exports&&!e.init||e.exportsFn||(e.exportsFn=q.makeShimExports(e)),i[t]=e}),y.shim=i),e.packages&&each(e.packages,function(e){var t,i;e="string"==typeof e?{name:e}:e,i=e.name,t=e.location,t&&(y.paths[i]=e.location),y.pkgs[i]=e.name+"/"+(e.main||"main").replace(currDirRegExp,"").replace(jsSuffixRegExp,"")}),eachProp(S,function(e,t){e.inited||e.map.unnormalized||(e.map=a(t,null,!0))}),(e.deps||e.callback)&&q.require(e.deps||[],e.callback)},makeShimExports:function(e){function t(){var t;return e.init&&(t=e.init.apply(global,arguments)),t||e.exports&&getGlobal(e.exports)}return t},makeRequire:function(t,n){function o(i,r,u){var d,p,f;return n.enableBuildCallback&&r&&isFunction(r)&&(r.__requireJsBuild=!0),"string"==typeof i?isFunction(r)?c(makeError("requireargs","Invalid require call"),u):t&&hasProp(E,i)?E[i](S[t.id]):req.get?req.get(q,i,t,o):(p=a(i,t,!1,!0),d=p.id,hasProp(j,d)?j[d]:c(makeError("notloaded",'Module name "'+d+'" has not been loaded yet for context: '+e+(t?"":". Use require([])")))):(v(),q.nextTick(function(){v(),f=s(a(null,t)),f.skipMap=n.skipMap,f.init(i,r,u,{enabled:!0}),l()}),o)}return n=n||{},mixin(o,{isBrowser:isBrowser,toUrl:function(e){var r,n=e.lastIndexOf("."),o=e.split("/")[0],a="."===o||".."===o;return n!==-1&&(!a||n>1)&&(r=e.substring(n,e.length),e=e.substring(0,n)),q.nameToUrl(i(e,t&&t.id,!0),r,!0)},defined:function(e){return hasProp(j,a(e,t,!1,!0).id)},specified:function(e){return e=a(e,t,!1,!0).id,hasProp(j,e)||hasProp(S,e)}}),t||(o.undef=function(e){d();var i=a(e,t,!0),n=getOwn(S,e);n.undefed=!0,r(e),delete j[e],delete P[i.url],delete M[e],eachReverse(O,function(t,i){t[0]===e&&O.splice(i,1)}),delete q.defQueueMap[e],n&&(n.events.defined&&(M[e]=n.events),p(e))}),o},enable:function(e){var t=getOwn(S,e.id);t&&s(e).enable()},completeLoad:function(e){var t,i,r,o=getOwn(y.shim,e)||{},a=o.exports;for(d();O.length;){if(i=O.shift(),null===i[0]){if(i[0]=e,t)break;t=!0}else i[0]===e&&(t=!0);h(i)}if(q.defQueueMap={},r=getOwn(S,e),!t&&!hasProp(j,e)&&r&&!r.inited){if(!(!y.enforceDefine||a&&getGlobal(a)))return n(e)?void 0:c(makeError("nodefine","No define call for "+e,null,[e]));h([e,o.deps||[],o.exportsFn])}l()},nameToUrl:function(e,t,i){var r,n,o,a,s,u,c,d=getOwn(y.pkgs,e);if(d&&(e=d),c=getOwn(R,e))return q.nameToUrl(c,t,i);if(req.jsExtRegExp.test(e))s=e+(t||"");else{for(r=y.paths,n=e.split("/"),o=n.length;o>0;o-=1)if(a=n.slice(0,o).join("/"),u=getOwn(r,a)){isArray(u)&&(u=u[0]),n.splice(0,o,u);break}s=n.join("/"),s+=t||(/^data\:|^blob\:|\?/.test(s)||i?"":".js"),s=("/"===s.charAt(0)||s.match(/^[\w\+\.\-]+:/)?"":y.baseUrl)+s}return y.urlArgs&&!/^blob\:/.test(s)?s+y.urlArgs(e,s):s},load:function(e,t){req.load(q,e,t)},execCb:function(e,t,i,r){return t.apply(r,i)},onScriptLoad:function(e){if("load"===e.type||readyRegExp.test((e.currentTarget||e.srcElement).readyState)){interactiveScript=null;var t=g(e);q.completeLoad(t.id)}},onScriptError:function(e){var t=g(e);if(!n(t.id)){var i=[];return eachProp(S,function(e,r){0!==r.indexOf("_@r")&&each(e.depMaps,function(e){if(e.id===t.id)return i.push(r),!0})}),c(makeError("scripterror",'Script error for "'+t.id+(i.length?'", needed by: '+i.join(", "):'"'),e,[t.id]))}}},q.require=q.makeRequire(),q}function getInteractiveScript(){return interactiveScript&&"interactive"===interactiveScript.readyState?interactiveScript:(eachReverse(scripts(),function(e){if("interactive"===e.readyState)return interactiveScript=e}),interactiveScript)}var req,s,head,baseElement,dataMain,src,interactiveScript,currentlyAddingScript,mainScript,subPath,version="2.3.2",commentRegExp=/\/\*[\s\S]*?\*\/|([^:"'=]|^)\/\/.*$/gm,cjsRequireRegExp=/[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,jsSuffixRegExp=/\.js$/,currDirRegExp=/^\.\//,op=Object.prototype,ostring=op.toString,hasOwn=op.hasOwnProperty,isBrowser=!("undefined"==typeof window||"undefined"==typeof navigator||!window.document),isWebWorker=!isBrowser&&"undefined"!=typeof importScripts,readyRegExp=isBrowser&&"PLAYSTATION 3"===navigator.platform?/^complete$/:/^(complete|loaded)$/,defContextName="_",isOpera="undefined"!=typeof opera&&"[object Opera]"===opera.toString(),contexts={},cfg={},globalDefQueue=[],useInteractive=!1;if("undefined"==typeof define){if("undefined"!=typeof requirejs){if(isFunction(requirejs))return;cfg=requirejs,requirejs=void 0}"undefined"==typeof require||isFunction(require)||(cfg=require,require=void 0),req=requirejs=function(e,t,i,r){var n,o,a=defContextName;return isArray(e)||"string"==typeof e||(o=e,isArray(t)?(e=t,t=i,i=r):e=[]),o&&o.context&&(a=o.context),n=getOwn(contexts,a),n||(n=contexts[a]=req.s.newContext(a)),o&&n.configure(o),n.require(e,t,i)},req.config=function(e){return req(e)},req.nextTick="undefined"!=typeof setTimeout?function(e){setTimeout(e,4)}:function(e){e()},require||(require=req),req.version=version,req.jsExtRegExp=/^\/|:|\?|\.js$/,req.isBrowser=isBrowser,s=req.s={contexts:contexts,newContext:newContext},req({}),each(["toUrl","undef","defined","specified"],function(e){req[e]=function(){var t=contexts[defContextName];return t.require[e].apply(t,arguments)}}),isBrowser&&(head=s.head=document.getElementsByTagName("head")[0],baseElement=document.getElementsByTagName("base")[0],baseElement&&(head=s.head=baseElement.parentNode)),req.onError=defaultOnError,req.createNode=function(e,t,i){var r=e.xhtml?document.createElementNS("http://www.w3.org/1999/xhtml","html:script"):document.createElement("script");return r.type=e.scriptType||"text/javascript",r.charset="utf-8",r.async=!0,r},req.load=function(e,t,i){var r,n=e&&e.config||{};if(isBrowser)return r=req.createNode(n,t,i),r.setAttribute("data-requirecontext",e.contextName),r.setAttribute("data-requiremodule",t),!r.attachEvent||r.attachEvent.toString&&r.attachEvent.toString().indexOf("[native code")<0||isOpera?(r.addEventListener("load",e.onScriptLoad,!1),r.addEventListener("error",e.onScriptError,!1)):(useInteractive=!0,r.attachEvent("onreadystatechange",e.onScriptLoad)),r.src=i,n.onNodeCreated&&n.onNodeCreated(r,n,t,i),currentlyAddingScript=r,baseElement?head.insertBefore(r,baseElement):head.appendChild(r),currentlyAddingScript=null,r;if(isWebWorker)try{setTimeout(function(){},0),importScripts(i),e.completeLoad(t)}catch(r){e.onError(makeError("importscripts","importScripts failed for "+t+" at "+i,r,[t]))}},isBrowser&&!cfg.skipDataMain&&eachReverse(scripts(),function(e){if(head||(head=e.parentNode),dataMain=e.getAttribute("data-main"))return mainScript=dataMain,cfg.baseUrl||mainScript.indexOf("!")!==-1||(src=mainScript.split("/"),mainScript=src.pop(),subPath=src.length?src.join("/")+"/":"./",cfg.baseUrl=subPath),mainScript=mainScript.replace(jsSuffixRegExp,""),req.jsExtRegExp.test(mainScript)&&(mainScript=dataMain),cfg.deps=cfg.deps?cfg.deps.concat(mainScript):[mainScript],!0}),define=function(e,t,i){var r,n;"string"!=typeof e&&(i=t,t=e,e=null),isArray(t)||(i=t,t=null),!t&&isFunction(i)&&(t=[],i.length&&(i.toString().replace(commentRegExp,commentReplace).replace(cjsRequireRegExp,function(e,i){t.push(i)}),t=(1===i.length?["require"]:["require","exports","module"]).concat(t))),useInteractive&&(r=currentlyAddingScript||getInteractiveScript(),r&&(e||(e=r.getAttribute("data-requiremodule")),n=contexts[r.getAttribute("data-requirecontext")])),n?(n.defQueue.push([e,t,i]),n.defQueueMap[e]=!0):globalDefQueue.push([e,t,i])},define.amd={jQuery:!0},req.exec=function(text){return eval(text)},req(cfg)}}(this,"undefined"==typeof setTimeout?void 0:setTimeout);
\ No newline at end of file diff --git a/lualib/lua_auth_results.lua b/lualib/lua_auth_results.lua index 3f604d760..4f4736ff1 100644 --- a/lualib/lua_auth_results.lua +++ b/lualib/lua_auth_results.lua @@ -78,7 +78,8 @@ local function gen_auth_results(task, settings) symbols = {} } - local mta_hostname = task:get_request_header('MTA-Tag') + local mta_hostname = task:get_request_header('MTA-Name') or + task:get_request_header('MTA-Tag') if mta_hostname then table.insert(hdr_parts, tostring(mta_hostname)) else diff --git a/lualib/lua_clickhouse.lua b/lualib/lua_clickhouse.lua new file mode 100644 index 000000000..37c9ebb7a --- /dev/null +++ b/lualib/lua_clickhouse.lua @@ -0,0 +1,314 @@ +--[[ +Copyright (c) 2018, Vsevolod Stakhov <vsevolod@highsecure.ru> +Copyright (c) 2018, Mikhail Galanin <mgalanin@mimecast.com> + +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. +]]-- + +local rspamd_logger = require "rspamd_logger" +local rspamd_http = require "rspamd_http" + +local exports = {} +local N = 'clickhouse' + +local default_timeout = 10.0 + +local function escape_spaces(query) + return query:gsub('%s', '%%20') +end + +local function clickhouse_quote(str) + if str then + return str:gsub('[\'\\]', '\\%1'):lower() + end + + return '' +end + +-- Converts an array to a string suitable for clickhouse +local function array_to_string(ar) + for i,elt in ipairs(ar) do + if type(elt) == 'string' then + ar[i] = '\'' .. clickhouse_quote(elt) .. '\'' + else + ar[i] = tostring(elt) + end + end + + return table.concat(ar, ',') +end + +-- Converts a row into TSV, taking extra care about arrays +local function row_to_tsv(row) + + for i,elt in ipairs(row) do + if type(elt) == 'table' then + row[i] = '[' .. array_to_string(elt) .. ']' + else + row[i] = tostring(elt) -- Assume there are no tabs there + end + end + + return table.concat(row, '\t') +end + +-- Parses JSONEachRow reply from CH +local function parse_clickhouse_response(params, data) + local lua_util = require "lua_util" + local ucl = require "ucl" + + rspamd_logger.debugm(N, params.log_obj, "got clickhouse response: %s", data) + if data == nil then + -- clickhouse returned no data (i.e. empty result set): exiting + return {} + end + + local function parse_string(s) + local parser = ucl.parser() + local res, err = parser:parse_string(s) + if not res then + rspamd_logger.errx(params.log_obj, 'Parser error: %s', err) + return nil + end + return parser:get_object() + end + + -- iterate over rows and parse + local ch_rows = lua_util.str_split(data, "\n") + local parsed_rows = {} + for _, plain_row in pairs(ch_rows) do + if plain_row and plain_row:len() > 1 then + local parsed_row = parse_string(plain_row) + if parsed_row then + table.insert(parsed_rows, parsed_row) + end + end + end + + return parsed_rows +end + +-- Helper to generate HTTP closure +local function mk_http_select_cb(upstream, params, ok_cb, fail_cb) + local function http_cb(err_message, code, data, _) + if code ~= 200 or err_message then + if not err_message then err_message = data end + local ip_addr = upstream:get_addr():to_string(true) + rspamd_logger.errx(params.log_obj, + "request failed on clickhouse server %s: %s", + ip_addr, err_message) + + if fail_cb then + fail_cb(params, err_message, data) + end + upstream:fail() + else + upstream:ok() + rspamd_logger.debugm(N, params.log_obj, + "http_cb ok: %s, %s, %s, %s", err_message, code, data, _) + local rows = parse_clickhouse_response(params, data) + + if rows then + if ok_cb then + ok_cb(params, rows) + end + else + if fail_cb then + fail_cb(params, 'failed to parse reply', data) + end + end + end + end + + return http_cb +end + +-- Helper to generate HTTP closure +local function mk_http_insert_cb(upstream, params, ok_cb, fail_cb) + local function http_cb(err_message, code, data, _) + if code ~= 200 or err_message then + if not err_message then err_message = data end + local ip_addr = upstream:get_addr():to_string(true) + rspamd_logger.errx(params.log_obj, + "request failed on clickhouse server %s: %s", + ip_addr, err_message) + + if fail_cb then + fail_cb(params, err_message, data) + end + upstream:fail() + else + upstream:ok() + rspamd_logger.debugm(N, params.log_obj, + "http_cb ok: %s, %s, %s, %s", err_message, code, data, _) + + if ok_cb then + ok_cb(params, data) + end + end + end + + return http_cb +end + +--[[[ +-- @function lua_clickhouse.select(upstream, settings, params, query, + ok_cb, fail_cb) +-- Make select request to clickhouse +-- @param {upstream} upstream clickhouse server upstream +-- @param {table} settings global settings table: +-- * use_gsip: use gzip compression +-- * timeout: request timeout +-- * no_ssl_verify: skip SSL verification +-- * user: HTTP user +-- * password: HTTP password +-- @param {params} HTTP request params +-- @param {string} query select query (passed in HTTP body) +-- @param {function} ok_cb callback to be called in case of success +-- @param {function} fail_cb callback to be called in case of some error +-- @return {boolean} whether a connection was successful +-- @example +-- +--]] +exports.select = function (upstream, settings, params, query, ok_cb, fail_cb) + local http_params = {} + + for k,v in pairs(params) do http_params[k] = v end + + http_params.callback = mk_http_select_cb(upstream, http_params, ok_cb, fail_cb) + http_params.gzip = settings.use_gzip + http_params.mime_type = 'text/plain' + http_params.timeout = settings.timeout or default_timeout + http_params.no_ssl_verify = settings.no_ssl_verify + http_params.user = settings.user + http_params.password = settings.password + http_params.body = query + http_params.log_obj = params.task or params.config + + rspamd_logger.debugm(N, http_params.log_obj, "clickhouse select request: %s", params.body) + + if not http_params.url then + local connect_prefix = "http://" + if settings.use_https then + connect_prefix = 'https://' + end + local ip_addr = upstream:get_addr():to_string(true) + http_params.url = connect_prefix .. ip_addr .. '/?default_format=JSONEachRow' + end + + return rspamd_http.request(http_params) +end + +--[[[ +-- @function lua_clickhouse.insert(upstream, settings, params, query, rows, + ok_cb, fail_cb) +-- Insert data rows to clickhouse +-- @param {upstream} upstream clickhouse server upstream +-- @param {table} settings global settings table: +-- * use_gsip: use gzip compression +-- * timeout: request timeout +-- * no_ssl_verify: skip SSL verification +-- * user: HTTP user +-- * password: HTTP password +-- @param {params} HTTP request params +-- @param {string} query select query (passed in `query` request element with spaces escaped) +-- @param {table|mixed} rows mix of strings, numbers or tables (for arrays) +-- @param {function} ok_cb callback to be called in case of success +-- @param {function} fail_cb callback to be called in case of some error +-- @return {boolean} whether a connection was successful +-- @example +-- +--]] +exports.insert = function (upstream, settings, params, query, rows, + ok_cb, fail_cb) + local fun = require "fun" + local http_params = {} + + for k,v in pairs(params) do http_params[k] = v end + + http_params.callback = mk_http_insert_cb(upstream, http_params, ok_cb, fail_cb) + http_params.gzip = settings.use_gzip + http_params.mime_type = 'text/plain' + http_params.timeout = settings.timeout or default_timeout + http_params.no_ssl_verify = settings.no_ssl_verify + http_params.user = settings.user + http_params.password = settings.password + http_params.method = 'POST' + http_params.body = {table.concat(fun.totable(fun.map(function(row) + return row_to_tsv(row) + end, rows)), '\n'), '\n'} + http_params.log_obj = params.task or params.config + + if not http_params.url then + local connect_prefix = "http://" + if settings.use_https then + connect_prefix = 'https://' + end + local ip_addr = upstream:get_addr():to_string(true) + http_params.url = string.format('%s%s/?query=%s%%20FORMAT%%20TabSeparated', + connect_prefix, + ip_addr, + escape_spaces(query)) + end + + return rspamd_http.request(http_params) +end + +--[[[ +-- @function lua_clickhouse.generic(upstream, settings, params, query, + ok_cb, fail_cb) +-- Make a generic request to Clickhouse (e.g. alter) +-- @param {upstream} upstream clickhouse server upstream +-- @param {table} settings global settings table: +-- * use_gsip: use gzip compression +-- * timeout: request timeout +-- * no_ssl_verify: skip SSL verification +-- * user: HTTP user +-- * password: HTTP password +-- @param {params} HTTP request params +-- @param {string} query Clickhouse query (passed in `query` request element with spaces escaped) +-- @param {function} ok_cb callback to be called in case of success +-- @param {function} fail_cb callback to be called in case of some error +-- @return {boolean} whether a connection was successful +-- @example +-- +--]] +exports.generic = function (upstream, settings, params, query, + ok_cb, fail_cb) + local http_params = {} + + for k,v in pairs(params) do http_params[k] = v end + + http_params.callback = mk_http_insert_cb(upstream, http_params, ok_cb, fail_cb) + http_params.gzip = settings.use_gzip + http_params.mime_type = 'text/plain' + http_params.timeout = settings.timeout or default_timeout + http_params.no_ssl_verify = settings.no_ssl_verify + http_params.user = settings.user + http_params.password = settings.password + http_params.log_obj = params.task or params.config + + if not http_params.url then + local connect_prefix = "http://" + if settings.use_https then + connect_prefix = 'https://' + end + local ip_addr = upstream:get_addr():to_string(true) + http_params.url = connect_prefix .. ip_addr .. '/?default_format=JSONEachRow' + end + + return rspamd_http.request(http_params) +end + + +return exports
\ No newline at end of file diff --git a/lualib/lua_meta.lua b/lualib/lua_meta.lua index 96404192d..0161eb5c9 100644 --- a/lualib/lua_meta.lua +++ b/lualib/lua_meta.lua @@ -380,6 +380,23 @@ local function rspamd_gen_metatokens(task) end exports.rspamd_gen_metatokens = rspamd_gen_metatokens +exports.gen_metatokens = rspamd_gen_metatokens + +local function rspamd_gen_metatokens_table(task) + local metatokens = {} + + for _,mt in ipairs(metafunctions) do + local ct = mt.cb(task) + for i,tok in ipairs(ct) do + metatokens[mt.desc[i]] = tok + end + end + + return metatokens +end + +exports.rspamd_gen_metatokens_table = rspamd_gen_metatokens_table +exports.gen_metatokens_table = rspamd_gen_metatokens_table local function rspamd_count_metatokens() local ipairs = ipairs @@ -392,5 +409,6 @@ local function rspamd_count_metatokens() end exports.rspamd_count_metatokens = rspamd_count_metatokens +exports.count_metatokens = rspamd_count_metatokens return exports diff --git a/lualib/lua_redis.lua b/lualib/lua_redis.lua index d22a4010c..33757b154 100644 --- a/lualib/lua_redis.lua +++ b/lualib/lua_redis.lua @@ -110,9 +110,9 @@ local function try_load_redis_servers(options, rspamd_config, result) result['password'] = options['password'] end - if read_only and not result.write_servers then + if read_only and not upstreams_write then result.read_only = true - elseif result.write_servers then + elseif upstreams_write then result.read_only = false end diff --git a/lualib/lua_squeeze_rules.lua b/lualib/lua_squeeze_rules.lua index a6e420fbd..9c751a33a 100644 --- a/lualib/lua_squeeze_rules.lua +++ b/lualib/lua_squeeze_rules.lua @@ -36,6 +36,8 @@ local function gen_lua_squeeze_function(order) return {data[1](task)} end + -- Too expensive to call :( + --logger.debugm(SN, task, 'call for: %s', data[2]) local status, ret = pcall(real_call) if not status then @@ -256,7 +258,8 @@ exports.squeeze_init = function() -- and create squeezed rules for k,v in pairs(squeezed_symbols) do local parent_symbol = get_ordered_symbol_name(v.order) - logger.debugm(SN, rspamd_config, 'added squeezed rule: %s (%s)', k, parent_symbol) + logger.debugm(SN, rspamd_config, 'added squeezed rule: %s (%s): %s', + k, parent_symbol, v) rspamd_config:register_symbol{ type = 'virtual', name = k, diff --git a/lualib/lua_stat.lua b/lualib/lua_stat.lua index 81b5d0ed8..b5eaafcd3 100644 --- a/lualib/lua_stat.lua +++ b/lualib/lua_stat.lua @@ -45,29 +45,31 @@ local function convert_bayes_schema(redis_params, symbol_spam, symbol_ham, expi -- KEYS[2]: hash key ('S' or 'H') -- KEYS[3]: expire local lua_script = [[ +redis.replicate_commands() local keys = redis.call('SMEMBERS', KEYS[1]..'_keys') local nconverted = 0 - for _,k in ipairs(keys) do - local elts = redis.call('HGETALL', k) + local cursor = redis.call('HSCAN', k, 0) local neutral_prefix = string.gsub(k, KEYS[1], 'RS') - local real_key - - for i,v in ipairs(elts) do - - if i % 2 ~= 0 then - real_key = v - else - local nkey = string.format('%s_%s', neutral_prefix, real_key) - redis.call('HSET', nkey, KEYS[2], v) - if KEYS[3] and tonumber(KEYS[3]) > 0 then - redis.call('EXPIRE', nkey, KEYS[3]) + local elts + while cursor[1] ~= "0" do + elts = cursor[2] + cursor = redis.call('HSCAN', k, cursor[1]) + local real_key + for i,v in ipairs(elts) do + if i % 2 ~= 0 then + real_key = v + else + local nkey = string.format('%s_%s', neutral_prefix, real_key) + redis.call('HSET', nkey, KEYS[2], v) + if KEYS[3] and tonumber(KEYS[3]) > 0 then + redis.call('EXPIRE', nkey, KEYS[3]) + end + nconverted = nconverted + 1 end - nconverted = nconverted + 1 end end end - return nconverted ]] diff --git a/lualib/lua_util.lua b/lualib/lua_util.lua index cf4636aa2..ba5843ff6 100644 --- a/lualib/lua_util.lua +++ b/lualib/lua_util.lua @@ -25,15 +25,33 @@ local rspamd_util = require "rspamd_util" local fun = require "fun" local split_grammar = {} +local spaces_split_grammar +local space = lpeg.S' \t\n\v\f\r' +local nospace = 1 - space +local ptrim = space^0 * lpeg.C((space^0 * nospace^1)^0) +local match = lpeg.match + local function rspamd_str_split(s, sep) - local gr = split_grammar[sep] - - if not gr then - local _sep = lpeg.P(sep) - local elem = lpeg.C((1 - _sep)^0) - local p = lpeg.Ct(elem * (_sep * elem)^0) - gr = p - split_grammar[sep] = gr + local gr + if not sep then + if not spaces_split_grammar then + local _sep = space + local elem = lpeg.C((1 - _sep)^0) + local p = lpeg.Ct(elem * (_sep * elem)^0) + spaces_split_grammar = p + end + + gr = spaces_split_grammar + else + gr = split_grammar[sep] + + if not gr then + local _sep = lpeg.P(sep) + local elem = lpeg.C((1 - _sep)^0) + local p = lpeg.Ct(elem * (_sep * elem)^0) + gr = p + split_grammar[sep] = gr + end end return gr:match(s) @@ -50,10 +68,6 @@ end exports.rspamd_str_split = rspamd_str_split exports.str_split = rspamd_str_split -local space = lpeg.S' \t\n\v\f\r' -local nospace = 1 - space -local ptrim = space^0 * lpeg.C((space^0 * nospace^1)^0) -local match = lpeg.match exports.rspamd_str_trim = function(s) return match(ptrim, s) end @@ -476,36 +490,71 @@ end exports.override_defaults = override_defaults --[[[ --- @function lua_util.extract_specific_urls(task, limit, [need_emails[, filter[, prefix]) +-- @function lua_util.extract_specific_urls(params) +-- params: { +- - task +- - limit <int> (default = 9999) +- - esld_limit <int> (default = 9999) n domains per eSLD (effective second level domain) + works only if number of unique eSLD less than `limit` +- - need_emails <bool> (default = false) +- - filter <callback> (default = nil) +- - prefix <string> cache prefix (default = nil) +-- } -- Apply heuristic in extracting of urls from task, this function -- tries its best to extract specific number of urls from a task based on -- their characteristics --]] -exports.extract_specific_urls = function(task, lim, need_emails, filter, prefix) +-- exports.extract_specific_urls = function(params_or_task, limit, need_emails, filter, prefix) +exports.extract_specific_urls = function(params_or_task, lim, need_emails, filter, prefix) + local default_params = { + limit = 9999, + esld_limit = 9999, + need_emails = false, + filter = nil, + prefix = nil + } + + local params + if type(params_or_task) == 'table' and type(lim) == 'nil' then + params = params_or_task + else + -- Deprecated call + params = { + task = params_or_task, + limit = lim, + need_emails = need_emails, + filter = filter, + prefix = prefix + } + end + for k,v in pairs(default_params) do + if not params[k] then params[k] = v end + end + + local cache_key - if prefix then - cache_key = prefix + if params.prefix then + cache_key = params.prefix else - cache_key = string.format('sp_urls_%d%s', lim, need_emails) + cache_key = string.format('sp_urls_%d%s', params.limit, params.need_emails) end - local cached = task:cache_get(cache_key) + local cached = params.task:cache_get(cache_key) if cached then return cached end - local urls = task:get_urls(need_emails) + local urls = params.task:get_urls(params.need_emails) if not urls then return {} end - if filter then urls = fun.totable(fun.filter(filter, urls)) end - - if #urls <= lim then - task:cache_set(cache_key, urls) + if params.filter then urls = fun.totable(fun.filter(params.filter, urls)) end + if #urls <= params.limit and #urls <= params.esld_limit then + params.task:cache_set(cache_key, urls) return urls end @@ -524,7 +573,9 @@ exports.extract_specific_urls = function(task, lim, need_emails, filter, prefix) eslds[esld] = {u} neslds = neslds + 1 else - table.insert(eslds[esld], u) + if #eslds[esld] < params.esld_limit then + table.insert(eslds[esld], u) + end end local parts = rspamd_str_split(esld, '.') @@ -544,7 +595,7 @@ exports.extract_specific_urls = function(task, lim, need_emails, filter, prefix) else if u:get_user() then table.insert(res, u) - elseif u:is_subject() then + elseif u:is_subject() or u:is_phished() then table.insert(res, u) end end @@ -552,31 +603,40 @@ exports.extract_specific_urls = function(task, lim, need_emails, filter, prefix) end end - lim = lim - #res - if lim <= 0 then lim = 1 end + local limit = params.limit + limit = limit - #res + if limit <= 0 then limit = 1 end - if neslds <= lim then + if neslds <= limit then -- We can get urls based on their eslds - while lim > 0 do + repeat + local item_found = false + for _,lurls in pairs(eslds) do - table.insert(res, table.remove(lurls)) - lim = lim - 1 + if #lurls > 0 then + table.insert(res, table.remove(lurls)) + limit = limit - 1 + item_found = true + end end - end - task:cache_set(cache_key, urls) + until limit <= 0 or not item_found + + params.task:cache_set(cache_key, urls) return res end - if ntlds <= lim then - while lim > 0 do + if ntlds <= limit then + while limit > 0 do for _,lurls in pairs(tlds) do - table.insert(res, table.remove(lurls)) - lim = lim - 1 + if #lurls > 0 then + table.insert(res, table.remove(lurls)) + limit = limit - 1 + end end end - task:cache_set(cache_key, urls) + params.task:cache_set(cache_key, urls) return res end @@ -593,14 +653,14 @@ exports.extract_specific_urls = function(task, lim, need_emails, filter, prefix) local tld2 = tlds[tlds_keys[ntlds - i]] table.insert(res, table.remove(tld1)) table.insert(res, table.remove(tld2)) - lim = lim - 2 + limit = limit - 2 - if lim <= 0 then + if limit <= 0 then break end end - task:cache_set(cache_key, urls) + params.task:cache_set(cache_key, urls) return res end diff --git a/lualib/rspamadm/confighelp.lua b/lualib/rspamadm/confighelp.lua index d477ff69b..c45c35da5 100644 --- a/lualib/rspamadm/confighelp.lua +++ b/lualib/rspamadm/confighelp.lua @@ -13,6 +13,8 @@ local parser = argparse() :name "rspamadm confighelp" :description "Shows help for the specified configuration options" :help_description_margin(32) +parser:argument "path":args "*" + :description('Optional config paths') parser:flag "--no-color" :description "Disable coloured output" parser:flag "--short" diff --git a/lualib/rspamadm/configwizard.lua b/lualib/rspamadm/configwizard.lua index 10134c575..fb4caf20c 100644 --- a/lualib/rspamadm/configwizard.lua +++ b/lualib/rspamadm/configwizard.lua @@ -406,21 +406,10 @@ end return ver ]] - conn:add_cmd('EVAL', {lua_script, '1', symbol_ham}) - local _,ver_ham = conn:exec() + conn:add_cmd('EVAL', {lua_script, '1', 'RS'}) + local _,ver = conn:exec() - conn:add_cmd('EVAL', {lua_script, '1', symbol_spam}) - local _,ver_spam = conn:exec() - - -- If one of the classes is missing we still can convert the other one - if ver_ham == 0 and ver_spam == 0 and ver_ham ~= ver_spam then - printf("Current statistics versions do not match: %s -> %s, %s -> %s", - symbol_ham, ver_ham, symbol_spam, ver_spam) - printf("Cannot convert statistics") - return false - end - - return true,tonumber(ver_ham) + return true,tonumber(ver) end local function check_expire(conn) @@ -454,8 +443,8 @@ return ttl if not r then return false end if ver ~= 2 then if not ver then - printf('Key "%s_version" has not been found in Redis for %s/%s', - symbol_ham) + printf('Key "RS_version" has not been found in Redis for %s/%s', + symbol_ham, symbol_spam) else printf("You are using an old schema version: %s for %s/%s", ver, symbol_ham, symbol_spam) diff --git a/lualib/rspamadm/keypair.lua b/lualib/rspamadm/keypair.lua index 518c9e65f..d882fafde 100644 --- a/lualib/rspamadm/keypair.lua +++ b/lualib/rspamadm/keypair.lua @@ -19,6 +19,7 @@ local rspamd_keypair = require "rspamd_cryptobox_keypair" local rspamd_pubkey = require "rspamd_cryptobox_pubkey" local rspamd_signature = require "rspamd_cryptobox_signature" local rspamd_crypto = require "rspamd_cryptobox" +local rspamd_util = require "rspamd_util" local ucl = require "ucl" local logger = require "rspamd_logger" @@ -30,6 +31,7 @@ local parser = argparse() :command_target("command") :require_command(false) +-- Generate subcommand local generate = parser:command "generate gen g" :description "Creates a new keypair" generate:flag "-s --sign" @@ -47,6 +49,8 @@ generate:mutex( :default(true) ) +-- Sign subcommand + local sign = parser:command "sign sig s" :description "Signs a file using keypair" sign:option "-k --keypair" @@ -61,6 +65,8 @@ sign:argument "file" :argname "<file>" :args "*" +-- Verify subcommand + local verify = parser:command "verify ver v" :description "Verifies a file using keypair or a public key" verify:mutex( @@ -85,7 +91,54 @@ verify:option "-s --suffix" :argname "<suffix>" :default("sig") --- Default command is generate, so duplicate options +-- Encrypt subcommand + +local encrypt = parser:command "encrypt crypt enc e" + :description "Encrypts a file using keypair (or a pubkey)" +encrypt:mutex( + encrypt:option "-p --pubkey" + :description "Load pubkey from the specified file" + :argname "<file>", + encrypt:option "-P --pubstring" + :description "Load pubkey from the base32 encoded string" + :argname "<base32>", + encrypt:option "-k --keypair" + :description "Get pubkey from the keypair file" + :argname "<file>" +) +encrypt:option "-s --suffix" + :description "Suffix for encrypted file" + :argname "<suffix>" + :default("enc") +encrypt:argument "file" + :description "File to encrypt" + :argname "<file>" + :args "*" +encrypt:flag "-r --rm" + :description "Remove unencrypted file" +encrypt:flag "-f --force" + :description "Remove destination file if it exists" + +-- Decrypt subcommand + +local decrypt = parser:command "decrypt dec d" + :description "Decrypts a file using keypair" +decrypt:option "-k --keypair" + :description "Get pubkey from the keypair file" + :argname "<file>" +decrypt:flag "-S --keep-suffix" + :description "Preserve suffix for decrypted file (overwrite encrypted)" +decrypt:argument "file" + :description "File to encrypt" + :argname "<file>" + :args "*" +decrypt:flag "-f --force" + :description "Remove destination file if it exists (implied with -S)" +decrypt:flag "-r --rm" + :description "Remove encrypted file" + +-- Default command is generate, so duplicate options to be compatible + parser:flag "-s --sign" :description "Generates a sign keypair instead of the encryption one" parser:flag "-n --nist" @@ -106,6 +159,26 @@ local function fatal(...) os.exit(1) end +local function ask_yes_no(greet, default) + local def_str + if default then + greet = greet .. "[Y/n]: " + def_str = "yes" + else + greet = greet .. "[y/N]: " + def_str = "no" + end + + local reply = rspamd_util.readline(greet) + + if not reply then os.exit(0) end + if #reply == 0 then reply = def_str end + reply = reply:lower() + if reply == 'y' or reply == 'yes' then return true end + + return false +end + local function generate_handler(opts) local mode = 'encryption' if opts.sign then @@ -245,6 +318,145 @@ local function verify_handler(opts) end end +local function encrypt_handler(opts) + if opts.file then + if type(opts.file) == 'string' then + opts.file = {opts.file} + end + else + parser:error('no files to sign') + end + + local pk + local alg = 'curve25519' + + if opts.keypair then + local ucl_parser = ucl.parser() + local res,err = ucl_parser:parse_file(opts.keypair) + + if not res then + fatal(string.format('cannot load %s: %s', opts.keypair, err)) + end + + local kp = rspamd_keypair.load(ucl_parser:get_object()) + + if not kp then + fatal("cannot load keypair: " .. opts.keypair) + end + + pk = kp:pk() + alg = kp:alg() + elseif opts.pubkey then + if opts.nist then alg = 'nist' end + pk = rspamd_pubkey.load(opts.pubkey, 'sign', alg) + elseif opts.pubstr then + if opts.nist then alg = 'nist' end + pk = rspamd_pubkey.create(opts.pubstr, 'sign', alg) + end + + if not pk then + fatal("cannot load keypair: " .. opts.keypair) + end + + for _,fname in ipairs(opts.file) do + local enc = rspamd_crypto.encrypt_file(pk, fname, alg) + + if not enc then + fatal(string.format("cannot encrypt %s\n", fname)) + end + + local out + if opts.suffix and #opts.suffix > 0 then + out = string.format('%s.%s', fname, opts.suffix) + else + out = string.format('%s', fname) + end + + if rspamd_util.file_exists(out) then + if opts.force or ask_yes_no(string.format('File %s already exists, overwrite?', + out), true) then + os.remove(out) + else + os.exit(1) + end + end + + enc:save_in_file(out) + + if opts.rm then + os.remove(fname) + io.write(string.format('encrypted %s (deleted) -> %s\n', fname, out)) + else + io.write(string.format('encrypted %s -> %s\n', fname, out)) + end + end +end + +local function decrypt_handler(opts) + if opts.file then + if type(opts.file) == 'string' then + opts.file = {opts.file} + end + else + parser:error('no files to decrypt') + end + if not opts.keypair then + parser:error("no keypair specified") + end + + local ucl_parser = ucl.parser() + local res,err = ucl_parser:parse_file(opts.keypair) + + if not res then + fatal(string.format('cannot load %s: %s', opts.keypair, err)) + end + + local kp = rspamd_keypair.load(ucl_parser:get_object()) + + if not kp then + fatal("cannot load keypair: " .. opts.keypair) + end + + for _,fname in ipairs(opts.file) do + local decrypted = rspamd_crypto.decrypt_file(kp, fname) + + if not decrypted then + fatal(string.format("cannot decrypt %s\n", fname)) + end + + local out + if not opts['keep-suffix'] then + -- Strip the last suffix + out = fname:match("^(.+)%..+$") + else + out = fname + end + + local removed = false + + if rspamd_util.file_exists(out) then + if (opts.force or opts['keep-suffix']) + or ask_yes_no(string.format('File %s already exists, overwrite?', out), true) then + os.remove(out) + removed = true + else + os.exit(1) + end + end + + if opts.rm then + os.remove(fname) + removed = true + end + + if removed then + io.write(string.format('decrypted %s (removed) -> %s\n', fname, out)) + else + io.write(string.format('decrypted %s -> %s\n', fname, out)) + end + end +end + local function handler(args) local opts = parser:parse(args) @@ -256,8 +468,12 @@ local function handler(args) sign_handler(opts) elseif command == 'verify' then verify_handler(opts) + elseif command == 'encrypt' then + encrypt_handler(opts) + elseif command == 'decrypt' then + decrypt_handler(opts) else - parser:error('command %s is not yet implemented', command) + parser:error('command %s is not implemented', command) end end diff --git a/lualib/rspamadm/mime.lua b/lualib/rspamadm/mime.lua new file mode 100644 index 000000000..affe4949f --- /dev/null +++ b/lualib/rspamadm/mime.lua @@ -0,0 +1,543 @@ +--[[ +Copyright (c) 2018, Vsevolod Stakhov <vsevolod@highsecure.ru> + +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. +]]-- + +local argparse = require "argparse" +local ansicolors = require "ansicolors" +--local rspamd_util = require "rspamd_util" +local rspamd_task = require "rspamd_task" +local rspamd_logger = require "rspamd_logger" +local lua_meta = require "lua_meta" +local rspamd_url = require "rspamd_url" +local lua_util = require "lua_util" +local ucl = require "ucl" + +-- Define command line options +local parser = argparse() + :name "rspamadm mime" + :description "Mime manipulations provided by Rspamd" + :help_description_margin(30) + :command_target("command") + :require_command(true) + +parser:option "-c --config" + :description "Path to config file" + :argname("<cfg>") + :default(rspamd_paths["CONFDIR"] .. "/" .. "rspamd.conf") +parser:mutex( + parser:flag "-j --json" + :description "JSON output", + parser:flag "-U --ucl" + :description "UCL output" +) +parser:flag "-C --compact" + :description "Use compactl format" +parser:flag "--no-file" + :description "Do not print filename" + +-- Extract subcommand +local extract = parser:command "extract ex e" + :description "Extracts data from MIME messages" +extract:argument "file" + :description "File to process" + :argname "<file>" + :args "+" + +extract:flag "-t --text" + :description "Extracts plain text data from a message" +extract:flag "-H --html" + :description "Extracts htm data from a message" +extract:option "-o --output" + :description "Output format ('raw', 'content', 'oneline', 'decoded', 'decoded_utf')" + :argname("<type>") + :convert { + raw = "raw", + content = "content", + oneline = "content_oneline", + decoded = "raw_parsed", + decoded_utf = "raw_utf" + } + :default "content" +extract:flag "-w --words" + :description "Extracts words" +extract:flag "-p --part" + :description "Show part info" +extract:flag "-s --structure" + :description "Show structure info (e.g. HTML tags)" + + +local stat = parser:command "stat st s" + :description "Extracts statistical data from MIME messages" +stat:argument "file" + :description "File to process" + :argname "<file>" + :args "+" +stat:mutex( + stat:flag "-m --meta" + :description "Lua metatokens", + stat:flag "-b --bayes" + :description "Bayes tokens", + stat:flag "-F --fuzzy" + :description "Fuzzy hashes" +) +stat:flag "-s --shingles" + :description "Show shingles for fuzzy hashes" + +local urls = parser:command "urls url u" + :description "Extracts URLs from MIME messages" +urls:argument "file" + :description "File to process" + :argname "<file>" + :args "+" +urls:mutex( + urls:flag "-t --tld" + :description "Get TLDs only", + urls:flag "-H --host" + :description "Get hosts only" +) + +urls:flag "-u --unique" + :description "Print only unique urls" +urls:flag "-s --sort" + :description "Sort output" +urls:flag "--count" + :description "Print count of each printed element" +urls:flag "-r --reverse" + :description "Reverse sort order" + +local function load_config(opts) + local _r,err = rspamd_config:load_ucl(opts['config']) + + if not _r then + rspamd_logger.errx('cannot parse %s: %s', opts['config'], err) + os.exit(1) + end + + _r,err = rspamd_config:parse_rcl({'logging', 'worker'}) + if not _r then + rspamd_logger.errx('cannot process %s: %s', opts['config'], err) + os.exit(1) + end +end + +local function load_task(opts, fname) + if not fname then + parser:error('no file specified') + end + + local res,task = rspamd_task.load_from_file(fname, rspamd_config) + + if not res then + parser:error(string.format('cannot read message from %s: %s', fname, + task)) + end + + if not task:process_message() then + parser:error(string.format('cannot read message from %s: %s', fname, + 'failed to parse')) + end + + return task +end + +local function highlight(fmt, ...) + return ansicolors.white .. string.format(fmt, ...) .. ansicolors.reset +end + +local function maybe_print_fname(opts, fname) + if not opts.json and not opts['no-file'] then + rspamd_logger.messagex(highlight('File: %s', fname)) + end +end + +-- Print elements in form +-- filename -> table of elements +local function print_elts(elts, opts, func) + local fun = require "fun" + + if opts.json or opts.ucl then + local fmt = 'json' + if opts.compact then fmt = 'json-compact' end + if opts.ucl then fmt = 'ucl' end + io.write(ucl.to_format(elts, fmt)) + else + fun.each(function(fname, elt) + + if not opts.json and not opts.ucl then + if func then + elt = fun.map(func, elt) + end + maybe_print_fname(opts, fname) + fun.each(function(e) + io.write(e) + io.write("\n") + end, elt) + end + end, elts) + end +end + +local function extract_handler(opts) + local out_elts = {} + local process_func + + if opts.words then + -- Enable stemming + rspamd_config:init_subsystem('langdet') + end + + local function maybe_print_text_part_info(part, out) + local fun = require "fun" + if opts.part then + local t = 'plain text' + if part:is_html() then + t = 'html' + end + + if not opts.json and not opts.ucl then + table.insert(out, + rspamd_logger.slog('Part: %s: %s, language: %s, size: %s (%s raw), words: %s', + part:get_mimepart():get_digest():sub(1,8), + t, + part:get_language(), + part:get_length(), part:get_raw_length(), + part:get_words_count())) + table.insert(out, + rspamd_logger.slog('Stats: %s', + fun.foldl(function(acc, k, v) + if acc ~= '' then + return string.format('%s, %s:%s', acc, k, v) + else + return string.format('%s:%s', k,v) + end + end, '', part:get_stats()))) + table.insert(out, '\n') + end + end + end + + local function maybe_print_mime_part_info(part, out) + if opts.part then + + if not opts.json and not opts.ucl then + table.insert(out, + rspamd_logger.slog('Mime Part: %s: %s/%s, filename: %s, size: %s', + part:get_digest():sub(1,8), + ({part:get_type()})[1], + ({part:get_type()})[2], + part:get_filename(), + part:get_length())) + end + end + end + + for _,fname in ipairs(opts.file) do + local task = load_task(opts, fname) + out_elts[fname] = {} + + if not opts.text and not opts.html then + opts.text = true + opts.html = true + end + + if opts.text or opts.html then + local mp = task:get_parts() or {} + + for _,mime_part in ipairs(mp) do + local how = opts.output + local part + if mime_part:is_text() then part = mime_part:get_text() end + + if part and opts.text and not part:is_html() then + maybe_print_text_part_info(part, out_elts[fname]) + if opts.words then + table.insert(out_elts[fname], table.concat(part:get_words(), ' ')) + else + table.insert(out_elts[fname], tostring(part:get_content(how))) + end + elseif part and opts.html and part:is_html() then + maybe_print_text_part_info(part, out_elts[fname]) + if opts.words then + table.insert(out_elts[fname], table.concat(part:get_words(), ' ')) + else + if opts.structure then + local hc = part:get_html() + local res = {} + process_func = function(k, v) + return rspamd_logger.slog("%s = %s", k, v) + end + + hc:foreach_tag('any', function(tag) + local elt = {} + local ex = tag:get_extra() + elt.tag = tag:get_type() + if ex then + elt.extra = ex + end + local content = tag:get_content() + if content then + elt.content = content + end + table.insert(res, elt) + end) + out_elts[fname] = res + else + table.insert(out_elts[fname], tostring(part:get_content(how))) + end + end + end + + if not part then + maybe_print_mime_part_info(mime_part, out_elts[fname]) + end + end + end + + table.insert(out_elts[fname], "") + + task:destroy() -- No automatic dtor + end + + print_elts(out_elts, opts, process_func) +end + +local function stat_handler(opts) + local fun = require "fun" + local out_elts = {} + + load_config(opts) + rspamd_url.init(rspamd_config:get_tld_path()) + rspamd_config:init_subsystem('langdet,stat') -- Needed to gen stat tokens + + local process_func + + for _,fname in ipairs(opts.file) do + local task = load_task(opts, fname) + out_elts[fname] = {} + + if opts.meta then + local mt = lua_meta.gen_metatokens_table(task) + out_elts[fname] = mt + process_func = function(k, v) + return string.format("%s = %s", k, v) + end + elseif opts.bayes then + local bt = task:get_stat_tokens() + out_elts[fname] = bt + process_func = function(e) + return string.format('%s (%d): "%s"+"%s", [%s]', e.data, e.win, e.t1 or "", + e.t2 or "", table.concat(fun.totable( + fun.map(function(k) return k end, e.flags)), ",")) + end + elseif opts.fuzzy then + local parts = task:get_parts() or {} + out_elts[fname] = {} + process_func = function(e) + local ret = string.format('part: %s(%s): %s', e.type, e.file or "", e.digest) + if opts.shingles and e.shingles then + local sgl = {} + for _,s in ipairs(e.shingles) do + table.insert(sgl, string.format('%s: %s+%s+%s', s[1], s[2], s[3], s[4])) + end + + ret = ret .. '\n' .. table.concat(sgl, '\n') + end + return ret + end + for _,part in ipairs(parts) do + if not part:is_multipart() then + local text = part:get_text() + + if text then + local digest,shingles = text:get_fuzzy_hashes(task:get_mempool()) + table.insert(out_elts[fname], { + digest = digest, + shingles = shingles, + type = string.format('%s/%s', + ({part:get_type()})[1], + ({part:get_type()})[2]) + }) + else + table.insert(out_elts[fname], { + digest = part:get_digest(), + file = part:get_filename(), + type = string.format('%s/%s', + ({part:get_type()})[1], + ({part:get_type()})[2]) + }) + end + end + end + end + + task:destroy() -- No automatic dtor + end + + print_elts(out_elts, opts, process_func) +end + +local function urls_handler(opts) + load_config(opts) + rspamd_url.init(rspamd_config:get_tld_path()) + local out_elts = {} + + if opts.json then rspamd_logger.messagex('[') end + + for _,fname in ipairs(opts.file) do + out_elts[fname] = {} + local task = load_task(opts, fname) + local elts = {} + + local function process_url(u) + local s + if opts.tld then + s = u:get_tld() + elseif opts.host then + s = u:get_host() + else + s = u:get_text() + end + + if opts.unique then + if elts[s] then + elts[s].count = elts[s].count + 1 + else + elts[s] = { + count = 1, + url = u:to_table() + } + end + else + if opts.json then + table.insert(elts, u) + else + table.insert(elts, s) + end + end + end + + for _,u in ipairs(task:get_urls(true)) do + process_url(u) + end + + local json_elts = {} + + local function process_elt(s, u) + if opts.unique then + -- s is string, u is {url = url, count = count } + if not opts.json then + if opts.count then + table.insert(json_elts, string.format('%s : %s', s, u.count)) + else + table.insert(json_elts, s) + end + else + local tb = u.url + tb.count = u.count + table.insert(json_elts, tb) + end + else + -- s is index, u is url or string + if opts.json then + table.insert(json_elts, u) + else + table.insert(json_elts, u) + end + end + end + + if opts.sort then + local sfunc + if opts.unique then + sfunc = function(t, a, b) + if t[a].count ~= t[b].count then + if opts.reverse then + return t[a].count > t[b].count + else + return t[a].count < t[b].count + end + else + -- Sort lexicography + if opts.reverse then + return a > b + else + return a < b + end + end + end + else + sfunc = function(t, a, b) + local va, vb + if opts.json then + va = t[a]:get_text() + vb = t[b]:get_text() + else + va = t[a] + vb = t[b] + end + if opts.reverse then + return va > vb + else + return va < vb + end + end + end + + + for s,u in lua_util.spairs(elts, sfunc) do + process_elt(s, u) + end + else + for s,u in pairs(elts) do + process_elt(s, u) + end + end + + out_elts[fname] = json_elts + + task:destroy() -- No automatic dtor + end + + print_elts(out_elts, opts) +end + +local function handler(args) + local opts = parser:parse(args) + + local command = opts.command + + if type(opts.file) == 'string' then + opts.file = {opts.file} + elseif type(opts.file) == 'none' then + opts.file = {} + end + + if command == 'extract' then + extract_handler(opts) + elseif command == 'stat' then + stat_handler(opts) + elseif command == 'urls' then + urls_handler(opts) + else + parser:error('command %s is not implemented', command) + end +end + +return { + name = 'mime', + aliases = {'mime_tool'}, + handler = handler, + description = parser._description +}
\ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 000000000..14ee2bd7b --- /dev/null +++ b/package.json @@ -0,0 +1,6 @@ +{ + "devDependencies": { + "eslint": "*" + }, + "eslintIgnore": ["*.min.js", "interface/js/lib/domReady.js"] +} diff --git a/rules/headers_checks.lua b/rules/headers_checks.lua index 43cbea23e..d1f972a46 100644 --- a/rules/headers_checks.lua +++ b/rules/headers_checks.lua @@ -217,14 +217,15 @@ local check_replyto_id = rspamd_config:register_callback_symbol('CHECK_REPLYTO', elseif from[1].domain and rt[1].domain then if (util.strequal_caseless(from[1].domain, rt[1].domain)) then task:insert_result('REPLYTO_DOM_EQ_FROM_DOM', 1.0) - else + else -- See if Reply-To matches the To address local to = task:get_recipients(2) if (to and to[1] and to[1].addr:lower() == rt[1].addr:lower()) then -- Ignore this for mailing-lists and automatic submissions if (not (task:get_header('List-Unsubscribe') or + task:get_header('X-To-Get-Off-This-List') or task:get_header('X-List') or - task:get_header('Auto-Submitted'))) + task:get_header('Auto-Submitted'))) then task:insert_result('REPLYTO_EQ_TO_ADDR', 1.0) end @@ -503,44 +504,48 @@ rspamd_config.HEADER_FORGED_MDN = { } local headers_unique = { - 'Content-Type', - 'Content-Transfer-Encoding', + ['Content-Type'] = 1.0, + ['Content-Transfer-Encoding'] = 1.0, -- https://tools.ietf.org/html/rfc5322#section-3.6 - 'Date', - 'From', - 'Sender', - 'Reply-To', - 'To', - 'Cc', - 'Bcc', - 'Message-ID', - 'In-Reply-To', - 'References', - 'Subject' + ['Date'] = 0.1, + ['From'] = 1.0, + ['Sender'] = 1.0, + ['Reply-To'] = 1.0, + ['To'] = 0.2, + ['Cc'] = 0.1, + ['Bcc'] = 0.1, + ['Message-ID'] = 0.7, + ['In-Reply-To'] = 0.7, + ['References'] = 0.3, + ['Subject'] = 0.7 } rspamd_config.MULTIPLE_UNIQUE_HEADERS = { callback = function(task) local res = 0 + local max_mult = 0.0 local res_tbl = {} - for _,hdr in ipairs(headers_unique) do - local h = task:get_header_full(hdr) + for hdr,mult in pairs(headers_unique) do + local hc = task:get_header_count(hdr) - if h and #h > 1 then + if hc > 1 then res = res + 1 table.insert(res_tbl, hdr) + if max_mult < mult then + max_mult = mult + end end end if res > 0 then - return true,res,table.concat(res_tbl, ',') + return true,max_mult,table.concat(res_tbl, ',') end return false end, - score = 5.0, + score = 7.0, group = 'headers', one_shot = true, description = 'Repeated unique headers' @@ -558,6 +563,27 @@ rspamd_config.MISSING_FROM = { group = 'headers', description = 'Missing From: header' } + +rspamd_config.MULTIPLE_FROM = { + callback = function(task) + local from = task:get_from('mime') + if from and from[1] then + if #from > 1 then + return true,1.0,table.concat( + fun.totable( + fun.map(function(a) return a.addr end, + fun.filter(function(a) return a.addr and a.addr ~= '' end, + from))), + ',') + end + end + return false + end, + score = 9.0, + group = 'headers', + description = 'Multiple addresses in From' +} + rspamd_config.MV_CASE = { callback = function (task) local mv = task:get_header('Mime-Version', true) diff --git a/rules/html.lua b/rules/html.lua index 56cf22f11..c324b9de1 100644 --- a/rules/html.lua +++ b/rules/html.lua @@ -168,38 +168,62 @@ rspamd_config.R_SUSPICIOUS_IMAGES = { description = 'Message contains many suspicious messages' } -rspamd_config.R_WHITE_ON_WHITE = { +local vis_check_id = rspamd_config:register_symbol{ + name = 'HTML_VISIBLE_CHECKS', + type = 'callback', callback = function(task) + --local logger = require "rspamd_logger" local tp = task:get_text_parts() -- get text parts in a message local ret = false local diff = 0.0 local transp_rate = 0 + local invisible_blocks = 0 + local zero_size_blocks = 0 local arg + local normal_len = 0 + local transp_len = 0 + for _,p in ipairs(tp) do -- iterate over text parts array using `ipairs` + normal_len = normal_len + p:get_length() if p:is_html() and p:get_html() then -- if the current part is html part - local normal_len = p:get_length() - local transp_len = 0 local hc = p:get_html() -- we get HTML context - hc:foreach_tag({'font', 'span', 'div', 'p'}, function(tag) + hc:foreach_tag({'font', 'span', 'div', 'p', 'td'}, function(tag) local bl = tag:get_extra() if bl then + if not bl['visible'] then + invisible_blocks = invisible_blocks + 1 + end + + if bl['font_size'] and bl['font_size'] == 0 then + zero_size_blocks = zero_size_blocks + 1 + end + if bl['bgcolor'] and bl['color'] and bl['visible'] then local color = bl['color'] local bgcolor = bl['bgcolor'] -- Should use visual approach here some day - local diff_r = math.abs(color[1] - bgcolor[1]) / 255.0 - local diff_g = math.abs(color[2] - bgcolor[2]) / 255.0 - local diff_b = math.abs(color[3] - bgcolor[3]) / 255.0 - diff = (diff_r + diff_g + diff_b) / 3.0 + local diff_r = math.abs(color[1] - bgcolor[1]) + local diff_g = math.abs(color[2] - bgcolor[2]) + local diff_b = math.abs(color[3] - bgcolor[3]) + local r_avg = (color[1] + bgcolor[1]) / 2.0 + -- Square + diff_r = diff_r * diff_r + diff_g = diff_g * diff_g + diff_b = diff_b * diff_b + + diff = math.sqrt(2*diff_r + 4*diff_g + 3 * diff_b + + (r_avg * (diff_r - diff_b) / 256.0)) + diff = diff / 256.0 if diff < 0.1 then ret = true - transp_len = (tag:get_content_length()) * - (0.1 - diff) * 5.0 - normal_len = normal_len - tag:get_content_length() + local content_len = #(tag:get_content() or {}) + invisible_blocks = invisible_blocks + 1 -- This block is invisible + transp_len = transp_len + content_len * (0.1 - diff) * 10.0 + normal_len = normal_len - content_len local tr = transp_len / (normal_len + transp_len) if tr > transp_rate then transp_rate = tr @@ -219,21 +243,91 @@ rspamd_config.R_WHITE_ON_WHITE = { end if ret then + transp_rate = transp_len / (normal_len + transp_len) + if transp_rate > 0.1 then if transp_rate > 0.5 or transp_rate ~= transp_rate then transp_rate = 0.5 end - return true,(transp_rate * 2.0),arg + + task:insert_result('R_WHITE_ON_WHITE', (transp_rate * 2.0), arg) end end - return false + if invisible_blocks > 0 then + if invisible_blocks > 10 then + invisible_blocks = 10 + end + local rates = { -- From 1 to 10 + 0.05, + 0.1, + 0.2, + 0.3, + 0.4, + 0.5, + 0.6, + 0.7, + 0.8, + 1.0, + } + task:insert_result('MANY_INVISIBLE_PARTS', rates[invisible_blocks], + tostring(invisible_blocks)) + end + + if zero_size_blocks > 0 then + if zero_size_blocks > 5 then + if zero_size_blocks > 10 then + -- Full score + task:insert_result('ZERO_FONT', 1.0, + tostring(zero_size_blocks)) + else + zero_size_blocks = 5 + end + end + + if zero_size_blocks <= 5 then + local rates = { -- From 1 to 5 + 0.1, + 0.2, + 0.2, + 0.3, + 0.5, + } + task:insert_result('ZERO_FONT', rates[zero_size_blocks], + tostring(zero_size_blocks)) + end + end end, +} +rspamd_config:register_symbol{ + type = 'virtual', + parent = vis_check_id, + name = 'R_WHITE_ON_WHITE', + description = 'Message contains low contrast text', score = 4.0, group = 'html', one_shot = true, - description = 'Message contains low contrast text' +} + +rspamd_config:register_symbol{ + type = 'virtual', + parent = vis_check_id, + name = 'ZERO_FONT', + description = 'Zero sized font used', + score = 1.0, -- Reached if more than 5 elements have zero size + one_shot = true, + group = 'html' +} + +rspamd_config:register_symbol{ + type = 'virtual', + parent = vis_check_id, + name = 'MANY_INVISIBLE_PARTS', + description = 'Many parts are visually hidden', + score = 1.0, -- Reached if more than 10 elements are hidden + one_shot = true, + group = 'html' } rspamd_config.EXT_CSS = { diff --git a/rules/regexp/headers.lua b/rules/regexp/headers.lua index a7300e2b8..3daa58c48 100644 --- a/rules/regexp/headers.lua +++ b/rules/regexp/headers.lua @@ -62,6 +62,13 @@ reconf['R_NO_SPACE_IN_FROM'] = { group = 'header' } +reconf['TO_WRAPPED_IN_SPACES'] = { + re = [[To=/<\s[-.\w]+\@[-.\w]+\s>/X]], + score = 2.0, + description = 'To address is wrapped in spaces inside angle brackets (e.g. display-name < local-part@domain >)', + group = 'header' +} + -- Detects missing Subject header reconf['MISSING_SUBJECT'] = { re = '!raw_header_exists(Subject)', @@ -269,7 +276,7 @@ local subj_encoded_qp = 'Subject=/\\=\\?\\S+\\?Q\\?/iX' reconf['SUBJ_EXCESS_QP'] = { re = string.format('%s & !%s', subj_encoded_qp, subj_needs_mime), score = 1.2, - description = 'Subect is unnecessarily encoded in quoted-printable', + description = 'Subject is unnecessarily encoded in quoted-printable', group = 'excessqp' } diff --git a/rules/rspamd.lua b/rules/rspamd.lua index 6b53828ee..a193eb495 100644 --- a/rules/rspamd.lua +++ b/rules/rspamd.lua @@ -23,6 +23,7 @@ rspamd_maps = {} -- Global maps local local_conf = rspamd_paths['CONFDIR'] local local_rules = rspamd_paths['RULESDIR'] +local rspamd_util = require "rspamd_util" dofile(local_rules .. '/regexp/headers.lua') dofile(local_rules .. '/regexp/misc.lua') @@ -36,26 +37,16 @@ dofile(local_rules .. '/http_headers.lua') dofile(local_rules .. '/forwarding.lua') dofile(local_rules .. '/mid.lua') -local function file_exists(filename) - local file = io.open(filename) - if file then - io.close(file) - return true - else - return false - end -end - -if file_exists(local_conf .. '/rspamd.local.lua') then +if rspamd_util.file_exists(local_conf .. '/rspamd.local.lua') then dofile(local_conf .. '/rspamd.local.lua') else -- Legacy lua/rspamd.local.lua - if file_exists(local_conf .. '/lua/rspamd.local.lua') then + if rspamd_util.file_exists(local_conf .. '/lua/rspamd.local.lua') then dofile(local_conf .. '/lua/rspamd.local.lua') end end -if file_exists(local_rules .. '/rspamd.classifiers.lua') then +if rspamd_util.file_exists(local_rules .. '/rspamd.classifiers.lua') then dofile(local_rules .. '/rspamd.classifiers.lua') end diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f01f7a45c..0f9cbd668 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -171,9 +171,7 @@ TARGET_LINK_LIBRARIES(rspamd rspamd-server) IF (ENABLE_SNOWBALL MATCHES "ON") TARGET_LINK_LIBRARIES(rspamd stemmer) ENDIF() -IF(ENABLE_HIREDIS MATCHES "ON") - TARGET_LINK_LIBRARIES(rspamd rspamd-hiredis) -ENDIF() +TARGET_LINK_LIBRARIES(rspamd rspamd-hiredis) IF (ENABLE_FANN MATCHES "ON") TARGET_LINK_LIBRARIES(rspamd fann) diff --git a/src/client/rspamc.c b/src/client/rspamc.c index 201e80403..072aa583d 100644 --- a/src/client/rspamc.c +++ b/src/client/rspamc.c @@ -141,7 +141,7 @@ static GOptionEntry entries[] = {"exclude", 0, 0, G_OPTION_ARG_STRING_ARRAY, &exclude_patterns, "Exclude specific glob patterns in file names (can be repeated)", NULL}, {"sort", 0, 0, G_OPTION_ARG_STRING, &sort, - "Sort output in a specific order (name, weight, time)", NULL}, + "Sort output in a specific order (name, weight, frequency, hits)", NULL}, { "empty", 'E', 0, G_OPTION_ARG_NONE, &empty_input, "Allow empty input instead of reading from stdin", NULL }, { "fuzzy-symbol", 'S', 0, G_OPTION_ARG_STRING, &fuzzy_symbol, @@ -990,6 +990,15 @@ rspamc_counters_sort (const ucl_object_t **o1, const ucl_object_t **o2) order2 = ucl_object_todouble (elt2) * 1000000; } } + else if (g_ascii_strcasecmp (args[0], "hits") == 0) { + elt1 = ucl_object_lookup (*o1, "hits"); + elt2 = ucl_object_lookup (*o2, "hits"); + + if (elt1 && elt2) { + order1 = ucl_object_toint (elt1); + order2 = ucl_object_toint (elt2); + } + } g_strfreev (args); } @@ -1001,9 +1010,9 @@ rspamc_counters_sort (const ucl_object_t **o1, const ucl_object_t **o2) static void rspamc_counters_output (FILE *out, ucl_object_t *obj) { - const ucl_object_t *cur, *sym, *weight, *freq, *freq_dev, *tim; + const ucl_object_t *cur, *sym, *weight, *freq, *freq_dev, *nhits; ucl_object_iter_t iter = NULL; - gchar fmt_buf[64], dash_buf[82]; + gchar fmt_buf[64], dash_buf[82], sym_buf[82]; gint l, max_len = INT_MIN, i; static const gint dashes = 44; @@ -1038,14 +1047,14 @@ rspamc_counters_output (FILE *out, ucl_object_t *obj) if (tty) { printf ("\033[1m"); } - printf (fmt_buf, "Pri", "Symbol", "Weight", "Frequency", "Time"); + printf (fmt_buf, "Pri", "Symbol", "Weight", "Frequency", "Hits"); printf (" %s \n", dash_buf); - printf (fmt_buf, "", "", "", "hits/sec", "usec"); + printf (fmt_buf, "", "", "", "hits/min", ""); if (tty) { printf ("\033[0m"); } rspamd_snprintf (fmt_buf, sizeof (fmt_buf), - "| %%3d | %%%ds | %%7.1f | %%6.3f(%%5.3f) | %%7.4f |\n", max_len); + "| %%3d | %%%ds | %%7.1f | %%6.3f(%%5.3f) | %%7ju |\n", max_len); iter = NULL; i = 0; @@ -1055,15 +1064,26 @@ rspamc_counters_output (FILE *out, ucl_object_t *obj) weight = ucl_object_lookup (cur, "weight"); freq = ucl_object_lookup (cur, "frequency"); freq_dev = ucl_object_lookup (cur, "frequency_stddev"); - tim = ucl_object_lookup (cur, "time"); + nhits = ucl_object_lookup (cur, "hits"); + + if (sym && weight && freq && nhits) { + const gchar *sym_name; + + if (sym->len > max_len) { + rspamd_snprintf (sym_buf, sizeof (sym_buf), "%*s...", + (max_len - 3), ucl_object_tostring (sym)); + sym_name = sym_buf; + } + else { + sym_name = ucl_object_tostring (sym); + } - if (sym && weight && freq && tim) { printf (fmt_buf, i, - ucl_object_tostring (sym), - ucl_object_todouble (weight), - ucl_object_todouble (freq), - ucl_object_todouble (freq_dev), - ucl_object_todouble (tim)); + sym_name, + ucl_object_todouble (weight), + ucl_object_todouble (freq) * 60.0, + ucl_object_todouble (freq_dev) * 60.0, + (uintmax_t)ucl_object_toint (nhits)); } i++; } diff --git a/src/controller.c b/src/controller.c index 4c08b5e13..d41860937 100644 --- a/src/controller.c +++ b/src/controller.c @@ -1351,7 +1351,7 @@ rspamd_controller_handle_legacy_history ( struct roll_history_row *row, *copied_rows; guint i, rows_proc, row_num; struct tm tm; - gchar timebuf[32]; + gchar timebuf[32], **syms; ucl_object_t *top, *obj; top = ucl_object_typed_new (UCL_ARRAY); @@ -1405,15 +1405,41 @@ rspamd_controller_handle_legacy_history ( ucl_object_fromdouble (0.0), "required_score", 0, false); } - ucl_object_insert_key (obj, ucl_object_fromstring ( - row->symbols), "symbols", 0, false); - ucl_object_insert_key (obj, ucl_object_fromint ( - row->len), "size", 0, false); - ucl_object_insert_key (obj, ucl_object_fromdouble ( - row->scan_time), "scan_time", 0, false); + syms = g_strsplit_set (row->symbols, ", ", -1); + + if (syms) { + guint nelts = g_strv_length (syms); + ucl_object_t *syms_obj = ucl_object_typed_new (UCL_OBJECT); + ucl_object_reserve (syms_obj, nelts); + + for (guint j = 0; j < nelts; j++) { + g_strstrip (syms[j]); + + if (strlen (syms[j]) == 0) { + /* Empty garbadge */ + continue; + } + + ucl_object_t *cur = ucl_object_typed_new (UCL_OBJECT); + + ucl_object_insert_key (cur, ucl_object_fromdouble (0.0), + "score", 0, false); + ucl_object_insert_key (syms_obj, cur, syms[j], 0, true); + } + + ucl_object_insert_key (obj, syms_obj, "symbols", 0, false); + g_strfreev (syms); + } + + ucl_object_insert_key (obj, ucl_object_fromint (row->len), + "size", 0, false); + ucl_object_insert_key (obj, + ucl_object_fromdouble (row->scan_time), + "scan_time", 0, false); + if (row->user[0] != '\0') { - ucl_object_insert_key (obj, ucl_object_fromstring ( - row->user), "user", 0, false); + ucl_object_insert_key (obj, ucl_object_fromstring (row->user), + "user", 0, false); } if (row->from_addr[0] != '\0') { ucl_object_insert_key (obj, ucl_object_fromstring ( @@ -3146,6 +3172,7 @@ rspamd_controller_store_saved_stats (struct rspamd_controller_worker_ctx *ctx) { struct rspamd_stat *stat; ucl_object_t *top, *sub; + struct ucl_emitter_functions *efuncs; gint i, fd; g_assert (ctx->saved_stats_path != NULL); @@ -3191,12 +3218,14 @@ rspamd_controller_store_saved_stats (struct rspamd_controller_worker_ctx *ctx) "control_connections", 0, false); + efuncs = ucl_object_emit_fd_funcs (fd); ucl_object_emit_full (top, UCL_EMIT_JSON_COMPACT, - ucl_object_emit_fd_funcs (fd), NULL); + efuncs, NULL); ucl_object_unref (top); rspamd_file_unlock (fd, FALSE); close (fd); + ucl_object_emit_funcs_free (efuncs); } static void @@ -3463,7 +3492,7 @@ static int lua_csession_send_string (lua_State *L) { struct rspamd_http_connection_entry *c = lua_check_controller_entry (L, 1); - const gchar *str = lua_tostring (L, 3); + const gchar *str = lua_tostring (L, 2); if (c) { rspamd_controller_send_string (c, str); @@ -3604,6 +3633,7 @@ start_controller_worker (struct rspamd_worker *worker) struct module_ctx *mctx; GHashTableIter iter; gpointer key, value; + guint i; struct rspamd_keypair_cache *cache; struct timeval stv; const guint save_stats_interval = 60 * 1000; /* 1 minute */ @@ -3763,9 +3793,7 @@ start_controller_worker (struct rspamd_worker *worker) rspamd_http_router_set_key (ctx->http, ctx->key); } - g_hash_table_iter_init (&iter, ctx->cfg->c_modules); - while (g_hash_table_iter_next (&iter, &key, &value)) { - mctx = value; + PTR_ARRAY_FOREACH (ctx->cfg->c_modules, i, mctx) { if (mctx->mod->module_attach_controller_func != NULL) { mctx->mod->module_attach_controller_func (mctx, ctx->custom_commands); @@ -3802,10 +3830,12 @@ start_controller_worker (struct rspamd_worker *worker) rspamd_worker_init_monitored (worker, ctx->ev_base, ctx->resolver); } - rspamd_map_watch (worker->srv->cfg, ctx->ev_base, ctx->resolver, TRUE); + rspamd_map_watch (worker->srv->cfg, ctx->ev_base, + ctx->resolver, worker, TRUE); } else { - rspamd_map_watch (worker->srv->cfg, ctx->ev_base, ctx->resolver, FALSE); + rspamd_map_watch (worker->srv->cfg, ctx->ev_base, + ctx->resolver, worker, FALSE); } rspamd_lua_run_postloads (ctx->cfg->lua_state, ctx->cfg, ctx->ev_base, worker); diff --git a/src/fuzzy_storage.c b/src/fuzzy_storage.c index 44b7c555e..cd0266844 100644 --- a/src/fuzzy_storage.c +++ b/src/fuzzy_storage.c @@ -17,6 +17,7 @@ * Rspamd fuzzy storage server */ +#include <src/libserver/fuzzy_wire.h> #include "config.h" #include "util.h" #include "rspamd.h" @@ -126,7 +127,6 @@ struct rspamd_fuzzy_storage_ctx { struct rspamd_config *cfg; /* END OF COMMON PART */ struct fuzzy_global_stat stat; - char *hashfile; gdouble expire; gdouble sync_timeout; struct rspamd_radix_map_helper *update_ips; @@ -154,6 +154,7 @@ struct rspamd_fuzzy_storage_ctx { GHashTable *keys; gboolean encrypted_only; gboolean collection_mode; + gboolean read_only; struct rspamd_cryptobox_keypair *collection_keypair; struct rspamd_cryptobox_pubkey *collection_sign_key; gchar *collection_id_file; @@ -240,6 +241,10 @@ rspamd_fuzzy_check_client (struct fuzzy_session *session, gboolean is_write) } if (is_write) { + if (session->ctx->read_only) { + return FALSE; + } + if (session->ctx->update_ips != NULL) { if (rspamd_match_radix_map_addr (session->ctx->update_ips, session->addr) == NULL) { @@ -314,6 +319,7 @@ struct rspamd_fuzzy_updates_cbdata { struct rspamd_http_message *msg; struct fuzzy_slave_connection *conn; struct rspamd_fuzzy_mirror *m; + GArray *updates_pending; }; static void @@ -335,12 +341,13 @@ fuzzy_mirror_updates_version_cb (guint64 rev64, void *ud) ctx = cbdata->ctx; msg = cbdata->msg; m = cbdata->m; - g_free (cbdata); + rev32 = GUINT32_TO_LE (rev32); len = sizeof (guint32) * 2; /* revision + last chunk */ - for (i = 0; i < ctx->updates_pending->len; i ++) { - io_cmd = &g_array_index (ctx->updates_pending, struct fuzzy_peer_cmd, i); + for (i = 0; i < cbdata->updates_pending->len; i ++) { + io_cmd = &g_array_index (cbdata->updates_pending, + struct fuzzy_peer_cmd, i); if (io_cmd->is_shingle) { len += sizeof (guint32) + sizeof (guint32) + @@ -356,8 +363,8 @@ fuzzy_mirror_updates_version_cb (guint64 rev64, void *ud) reply = rspamd_fstring_append (reply, (const char *)&rev32, sizeof (rev32)); - for (i = 0; i < ctx->updates_pending->len; i ++) { - io_cmd = &g_array_index (ctx->updates_pending, struct fuzzy_peer_cmd, i); + for (i = 0; i < cbdata->updates_pending->len; i ++) { + io_cmd = &g_array_index (cbdata->updates_pending, struct fuzzy_peer_cmd, i); if (io_cmd->is_shingle) { len = sizeof (guint32) + @@ -384,13 +391,17 @@ fuzzy_mirror_updates_version_cb (guint64 rev64, void *ud) conn->sock, &tv, ctx->ev_base); msg_info ("send update request to %s", m->name); + + g_array_free (cbdata->updates_pending, TRUE); + g_free (cbdata); } static void fuzzy_mirror_updates_to_http (struct rspamd_fuzzy_mirror *m, - struct fuzzy_slave_connection *conn, - struct rspamd_fuzzy_storage_ctx *ctx, - struct rspamd_http_message *msg) + struct fuzzy_slave_connection *conn, + struct rspamd_fuzzy_storage_ctx *ctx, + struct rspamd_http_message *msg, + GArray *updates) { struct rspamd_fuzzy_updates_cbdata *cbdata; @@ -400,6 +411,10 @@ fuzzy_mirror_updates_to_http (struct rspamd_fuzzy_mirror *m, cbdata->msg = msg; cbdata->conn = conn; cbdata->m = m; + /* Copy queue */ + cbdata->updates_pending = g_array_sized_new (FALSE, FALSE, + sizeof (struct fuzzy_peer_cmd), updates->len); + g_array_append_vals (cbdata->updates_pending, updates->data, updates->len); rspamd_fuzzy_backend_version (ctx->backend, local_db_name, fuzzy_mirror_updates_version_cb, cbdata); } @@ -431,7 +446,7 @@ fuzzy_mirror_finish_handler (struct rspamd_http_connection *conn, static void rspamd_fuzzy_send_update_mirror (struct rspamd_fuzzy_storage_ctx *ctx, - struct rspamd_fuzzy_mirror *m) + struct rspamd_fuzzy_mirror *m, GArray *updates) { struct fuzzy_slave_connection *conn; struct rspamd_http_message *msg; @@ -452,7 +467,7 @@ rspamd_fuzzy_send_update_mirror (struct rspamd_fuzzy_storage_ctx *ctx, if (conn->sock == -1) { msg_err ("cannot connect upstream for %s", m->name); - rspamd_upstream_fail (conn->up); + rspamd_upstream_fail (conn->up, TRUE); return; } @@ -470,10 +485,11 @@ rspamd_fuzzy_send_update_mirror (struct rspamd_fuzzy_storage_ctx *ctx, rspamd_http_connection_set_key (conn->http_conn, ctx->sync_keypair); msg->peer_key = rspamd_pubkey_ref (m->key); - fuzzy_mirror_updates_to_http (m, conn, ctx, msg); + fuzzy_mirror_updates_to_http (m, conn, ctx, msg, updates); } struct rspamd_updates_cbdata { + GArray *updates_pending; struct rspamd_fuzzy_storage_ctx *ctx; gchar *source; }; @@ -505,7 +521,12 @@ rspamd_fuzzy_stat_callback (gint fd, gshort what, gpointer ud) } static void -rspamd_fuzzy_updates_cb (gboolean success, void *ud) +rspamd_fuzzy_updates_cb (gboolean success, + guint nadded, + guint ndeleted, + guint nextended, + guint nignored, + void *ud) { struct rspamd_updates_cbdata *cbdata = ud; struct rspamd_fuzzy_mirror *m; @@ -523,14 +544,17 @@ rspamd_fuzzy_updates_cb (gboolean success, void *ud) for (i = 0; i < ctx->mirrors->len; i ++) { m = g_ptr_array_index (ctx->mirrors, i); - rspamd_fuzzy_send_update_mirror (ctx, m); + rspamd_fuzzy_send_update_mirror (ctx, m, + cbdata->updates_pending); } } - msg_info ("successfully updated fuzzy storage: %d updates processed", - ctx->updates_pending->len); - /* Clear updates */ - ctx->updates_pending->len = 0; + msg_info ("successfully updated fuzzy storage: %d updates in queue; " + "%d pending currently; " + "%d added, %d deleted, %d extended, %d duplicates", + cbdata->updates_pending->len, + ctx->updates_pending->len, + nadded, ndeleted, nextended, nignored); rspamd_fuzzy_backend_version (ctx->backend, source, fuzzy_update_version_callback, g_strdup (source)); ctx->updates_failed = 0; @@ -538,17 +562,22 @@ rspamd_fuzzy_updates_cb (gboolean success, void *ud) else { if (++ctx->updates_failed > ctx->updates_maxfail) { msg_err ("cannot commit update transaction to fuzzy backend, discard " - "%ud updates after %d retries", - ctx->updates_pending->len, + "%ud updates after %d retries", + cbdata->updates_pending->len, ctx->updates_maxfail); ctx->updates_failed = 0; - ctx->updates_pending->len = 0; } else { msg_err ("cannot commit update transaction to fuzzy backend, " - "%ud updates are still pending, %d updates left", + "%ud updates are still left; %ud currently pending;" + " %d updates left", + cbdata->updates_pending->len, ctx->updates_pending->len, ctx->updates_maxfail - ctx->updates_failed); + /* Move the remaining updates to ctx queue */ + g_array_append_vals (ctx->updates_pending, + cbdata->updates_pending->data, + cbdata->updates_pending->len); } } @@ -562,6 +591,7 @@ rspamd_fuzzy_updates_cb (gboolean success, void *ud) event_base_loopexit (ctx->ev_base, &tv); } + g_array_free (cbdata->updates_pending, TRUE); g_free (cbdata->source); g_free (cbdata); } @@ -576,8 +606,13 @@ rspamd_fuzzy_process_updates_queue (struct rspamd_fuzzy_storage_ctx *ctx, if ((forced ||ctx->updates_pending->len > 0)) { cbdata = g_malloc (sizeof (*cbdata)); cbdata->ctx = ctx; + cbdata->updates_pending = ctx->updates_pending; + ctx->updates_pending = g_array_sized_new (FALSE, FALSE, + sizeof (struct fuzzy_peer_cmd), + MAX (cbdata->updates_pending->len, 1024)); cbdata->source = g_strdup (source); - rspamd_fuzzy_backend_process_updates (ctx->backend, ctx->updates_pending, + rspamd_fuzzy_backend_process_updates (ctx->backend, + cbdata->updates_pending, source, rspamd_fuzzy_updates_cb, cbdata); } } @@ -772,6 +807,8 @@ rspamd_fuzzy_check_callback (struct rspamd_fuzzy_reply *result, void *ud) struct fuzzy_session *session = ud; gboolean encrypted = FALSE, is_shingle = FALSE; struct rspamd_fuzzy_cmd *cmd = NULL; + const struct rspamd_shingle *shingle = NULL; + struct rspamd_shingle sgl_cpy; switch (session->cmd_type) { case CMD_NORMAL: @@ -779,6 +816,8 @@ rspamd_fuzzy_check_callback (struct rspamd_fuzzy_reply *result, void *ud) break; case CMD_SHINGLE: cmd = &session->cmd.shingle.basic; + memcpy (&sgl_cpy, &session->cmd.shingle.sgl, sizeof (sgl_cpy)); + shingle = &sgl_cpy; is_shingle = TRUE; break; case CMD_ENCRYPTED_NORMAL: @@ -787,12 +826,60 @@ rspamd_fuzzy_check_callback (struct rspamd_fuzzy_reply *result, void *ud) break; case CMD_ENCRYPTED_SHINGLE: cmd = &session->cmd.enc_shingle.cmd.basic; + memcpy (&sgl_cpy, &session->cmd.enc_shingle.cmd.sgl, sizeof (sgl_cpy)); + shingle = &sgl_cpy; encrypted = TRUE; is_shingle = TRUE; break; } rspamd_fuzzy_make_reply (cmd, result, session, encrypted, is_shingle); + + /* Refresh hash if found with strong confidence */ + if (result->v1.prob > 0.9 && !session->ctx->read_only) { + struct fuzzy_peer_cmd up_cmd; + struct fuzzy_peer_request *up_req; + + if (session->worker->index == 0 || session->ctx->peer_fd == -1) { + /* Just add to the queue */ + memset (&up_cmd, 0, sizeof (up_cmd)); + up_cmd.is_shingle = is_shingle; + memcpy (up_cmd.cmd.normal.digest, result->digest, + sizeof (up_cmd.cmd.normal.digest)); + up_cmd.cmd.normal.flag = result->v1.flag; + up_cmd.cmd.normal.cmd = FUZZY_REFRESH; + up_cmd.cmd.normal.shingles_count = cmd->shingles_count; + + if (is_shingle && shingle) { + memcpy (&up_cmd.cmd.shingle.sgl, shingle, + sizeof (up_cmd.cmd.shingle.sgl)); + } + + g_array_append_val (session->ctx->updates_pending, up_cmd); + } + else { + /* We need to send request to the peer */ + up_req = g_malloc0 (sizeof (*up_req)); + up_req->cmd.is_shingle = is_shingle; + + memcpy (up_req->cmd.cmd.normal.digest, result->digest, + sizeof (up_req->cmd.cmd.normal.digest)); + up_req->cmd.cmd.normal.flag = result->v1.flag; + up_req->cmd.cmd.normal.cmd = FUZZY_REFRESH; + up_req->cmd.cmd.normal.shingles_count = cmd->shingles_count; + + if (is_shingle && shingle) { + memcpy (&up_req->cmd.cmd.shingle.sgl, shingle, + sizeof (up_req->cmd.cmd.shingle.sgl)); + } + + event_set (&up_req->io_ev, session->ctx->peer_fd, EV_WRITE, + fuzzy_peer_send_io, up_req); + event_base_set (session->ctx->ev_base, &up_req->io_ev); + event_add (&up_req->io_ev, NULL); + } + } + REF_RELEASE (session); } @@ -1060,7 +1147,7 @@ rspamd_fuzzy_decrypt_command (struct fuzzy_session *s) /* Now decrypt request */ if (!rspamd_cryptobox_decrypt_nm_inplace (payload, payload_len, hdr->nonce, - rspamd_pubkey_get_nm (rk), + rspamd_pubkey_get_nm (rk, key->key), hdr->mac, RSPAMD_CRYPTOBOX_MODE_25519)) { msg_err ("decryption failed"); rspamd_pubkey_unref (rk); @@ -1068,7 +1155,7 @@ rspamd_fuzzy_decrypt_command (struct fuzzy_session *s) return FALSE; } - memcpy (s->nm, rspamd_pubkey_get_nm (rk), sizeof (s->nm)); + memcpy (s->nm, rspamd_pubkey_get_nm (rk, key->key), sizeof (s->nm)); rspamd_pubkey_unref (rk); return TRUE; @@ -2408,42 +2495,6 @@ init_fuzzy (struct rspamd_config *cfg) rspamd_rcl_register_worker_option (cfg, type, - "hashfile", - rspamd_rcl_parse_struct_string, - ctx, - G_STRUCT_OFFSET (struct rspamd_fuzzy_storage_ctx, hashfile), - 0, - "Path to fuzzy database"); - - rspamd_rcl_register_worker_option (cfg, - type, - "hash_file", - rspamd_rcl_parse_struct_string, - ctx, - G_STRUCT_OFFSET (struct rspamd_fuzzy_storage_ctx, hashfile), - 0, - "Path to fuzzy database (alias for hashfile)"); - - rspamd_rcl_register_worker_option (cfg, - type, - "file", - rspamd_rcl_parse_struct_string, - ctx, - G_STRUCT_OFFSET (struct rspamd_fuzzy_storage_ctx, hashfile), - 0, - "Path to fuzzy database (alias for hashfile)"); - - rspamd_rcl_register_worker_option (cfg, - type, - "database", - rspamd_rcl_parse_struct_string, - ctx, - G_STRUCT_OFFSET (struct rspamd_fuzzy_storage_ctx, hashfile), - 0, - "Path to fuzzy database (alias for hashfile)"); - - rspamd_rcl_register_worker_option (cfg, - type, "sync", rspamd_rcl_parse_struct_time, ctx, @@ -2504,6 +2555,15 @@ init_fuzzy (struct rspamd_config *cfg) rspamd_rcl_register_worker_option (cfg, type, + "read_only", + rspamd_rcl_parse_struct_boolean, + ctx, + G_STRUCT_OFFSET (struct rspamd_fuzzy_storage_ctx, read_only), + 0, + "Work in read only mode"); + + rspamd_rcl_register_worker_option (cfg, + type, "master_timeout", rspamd_rcl_parse_struct_time, ctx, @@ -2880,7 +2940,10 @@ start_fuzzy (struct rspamd_worker *worker) struct rspamd_map *m; if ((m = rspamd_map_add_from_ucl (cfg, ctx->skip_map, - "Skip hashes", rspamd_kv_list_read, rspamd_kv_list_fin, + "Skip hashes", + rspamd_kv_list_read, + rspamd_kv_list_fin, + rspamd_kv_list_dtor, (void **)&ctx->skip_hashes)) == NULL) { msg_warn_config ("cannot load hashes list from %s", ucl_object_tostring (ctx->skip_map)); @@ -2900,7 +2963,7 @@ start_fuzzy (struct rspamd_worker *worker) ctx->resolver = dns_resolver_init (worker->srv->logger, ctx->ev_base, worker->srv->cfg); - rspamd_map_watch (worker->srv->cfg, ctx->ev_base, ctx->resolver, 0); + rspamd_map_watch (worker->srv->cfg, ctx->ev_base, ctx->resolver, worker, 0); /* Get peer pipe */ memset (&srv_cmd, 0, sizeof (srv_cmd)); diff --git a/src/libcryptobox/keypair.c b/src/libcryptobox/keypair.c index 21b497130..ee9fa4649 100644 --- a/src/libcryptobox/keypair.c +++ b/src/libcryptobox/keypair.c @@ -444,12 +444,19 @@ rspamd_pubkey_from_bin (const guchar *raw, const guchar * -rspamd_pubkey_get_nm (struct rspamd_cryptobox_pubkey *p) +rspamd_pubkey_get_nm (struct rspamd_cryptobox_pubkey *p, + struct rspamd_cryptobox_keypair *kp) { g_assert (p != NULL); if (p->nm) { - return p->nm->nm; + if (memcmp (kp->id, (const guchar *)&p->nm->sk_id, sizeof (guint64)) == 0) { + return p->nm->nm; + } + + /* Wrong ID, need to recalculate */ + REF_RELEASE (p->nm); + p->nm = NULL; } return NULL; @@ -468,6 +475,7 @@ rspamd_pubkey_calculate_nm (struct rspamd_cryptobox_pubkey *p, abort (); } + memcpy (&p->nm->sk_id, kp->id, sizeof (guint64)); REF_INIT_RETAIN (p->nm, rspamd_cryptobox_nm_dtor); } @@ -978,6 +986,7 @@ rspamd_keypair_decrypt (struct rspamd_cryptobox_keypair *kp, return TRUE; } + gboolean rspamd_keypair_encrypt (struct rspamd_cryptobox_keypair *kp, const guchar *in, gsize inlen, @@ -1026,4 +1035,53 @@ rspamd_keypair_encrypt (struct rspamd_cryptobox_keypair *kp, } return TRUE; +} + +gboolean +rspamd_pubkey_encrypt (struct rspamd_cryptobox_pubkey *pk, + const guchar *in, gsize inlen, + guchar **out, gsize *outlen, + GError **err) +{ + guchar *nonce, *mac, *data, *pubkey; + struct rspamd_cryptobox_keypair *local; + gsize olen; + + g_assert (pk != NULL); + g_assert (in != NULL); + + if (pk->type != RSPAMD_KEYPAIR_KEX) { + g_set_error (err, rspamd_keypair_quark (), EINVAL, + "invalid pubkey type"); + + return FALSE; + } + + local = rspamd_keypair_new (pk->type, pk->alg); + + olen = inlen + sizeof (encrypted_magic) + + rspamd_cryptobox_pk_bytes (pk->alg) + + rspamd_cryptobox_mac_bytes (pk->alg) + + rspamd_cryptobox_nonce_bytes (pk->alg); + *out = g_malloc (olen); + memcpy (*out, encrypted_magic, sizeof (encrypted_magic)); + pubkey = *out + sizeof (encrypted_magic); + mac = pubkey + rspamd_cryptobox_pk_bytes (pk->alg); + nonce = mac + rspamd_cryptobox_mac_bytes (pk->alg); + data = nonce + rspamd_cryptobox_nonce_bytes (pk->alg); + + ottery_rand_bytes (nonce, rspamd_cryptobox_nonce_bytes (pk->alg)); + memcpy (data, in, inlen); + memcpy (pubkey, rspamd_pubkey_get_pk (pk, NULL), + rspamd_cryptobox_pk_bytes (pk->alg)); + rspamd_cryptobox_encrypt_inplace (data, inlen, nonce, pubkey, + rspamd_keypair_component (local, RSPAMD_KEYPAIR_COMPONENT_SK, NULL), + mac, pk->alg); + rspamd_keypair_unref (local); + + if (outlen) { + *outlen = olen; + } + + return TRUE; }
\ No newline at end of file diff --git a/src/libcryptobox/keypair.h b/src/libcryptobox/keypair.h index 3e78e7cbb..92af13b68 100644 --- a/src/libcryptobox/keypair.h +++ b/src/libcryptobox/keypair.h @@ -139,7 +139,8 @@ enum rspamd_cryptobox_mode rspamd_pubkey_alg (struct rspamd_cryptobox_pubkey *p) * @param p * @return */ -const guchar * rspamd_pubkey_get_nm (struct rspamd_cryptobox_pubkey *p); +const guchar * rspamd_pubkey_get_nm (struct rspamd_cryptobox_pubkey *p, + struct rspamd_cryptobox_keypair *kp); /** * Calculate and store nm value for the specified local key (performs ECDH) @@ -303,6 +304,21 @@ gboolean rspamd_keypair_encrypt (struct rspamd_cryptobox_keypair *kp, const guchar *in, gsize inlen, guchar **out, gsize *outlen, GError **err); - +/** + * Encrypts data usign specific pubkey (must have KEX type). + * This method actually generates ephemeral local keypair, use public key from + * the remote keypair and encrypts data + * @param kp keypair + * @param in raw input + * @param inlen input length + * @param out output (allocated internally using g_malloc) + * @param outlen output size + * @param err pointer to error + * @return TRUE if encryption has been completed, out must be freed in this case + */ +gboolean rspamd_pubkey_encrypt (struct rspamd_cryptobox_pubkey *pk, + const guchar *in, gsize inlen, + guchar **out, gsize *outlen, + GError **err); #endif /* SRC_LIBCRYPTOBOX_KEYPAIR_H_ */ diff --git a/src/libcryptobox/keypair_private.h b/src/libcryptobox/keypair_private.h index d91d1c68e..78b894d38 100644 --- a/src/libcryptobox/keypair_private.h +++ b/src/libcryptobox/keypair_private.h @@ -25,6 +25,7 @@ */ struct RSPAMD_ALIGNED(32) rspamd_cryptobox_nm { guchar RSPAMD_ALIGNED(32) nm[rspamd_cryptobox_MAX_NMBYTES]; + guint64 sk_id; /* Used to store secret key id */ ref_entry_t ref; }; diff --git a/src/libcryptobox/keypairs_cache.c b/src/libcryptobox/keypairs_cache.c index 176340712..5e3a13e18 100644 --- a/src/libcryptobox/keypairs_cache.c +++ b/src/libcryptobox/keypairs_cache.c @@ -105,6 +105,7 @@ rspamd_keypair_cache_process (struct rspamd_keypair_cache *c, memcpy (new->pair, rk->id, rspamd_cryptobox_HASHBYTES); memcpy (&new->pair[rspamd_cryptobox_HASHBYTES], lk->id, rspamd_cryptobox_HASHBYTES); + memcpy (&new->nm->sk_id, lk->id, sizeof (guint64)); if (rk->alg == RSPAMD_CRYPTOBOX_MODE_25519) { struct rspamd_cryptobox_pubkey_25519 *rk_25519 = diff --git a/src/libmime/email_addr.c b/src/libmime/email_addr.c index a0ad9ac19..50c293b35 100644 --- a/src/libmime/email_addr.c +++ b/src/libmime/email_addr.c @@ -372,7 +372,7 @@ rspamd_email_address_from_mime (rspamd_mempool_t *pool, obraces ++; } else if (*p == ')') { - ebraces --; + ebraces ++; } if (obraces == ebraces) { diff --git a/src/libmime/filter.c b/src/libmime/filter.c index c9367514f..81f8818bb 100644 --- a/src/libmime/filter.c +++ b/src/libmime/filter.c @@ -22,6 +22,25 @@ #include <math.h> #include "contrib/uthash/utlist.h" +/* Average symbols count to optimize hash allocation */ +static struct rspamd_counter_data symbols_count; + +static void +rspamd_metric_result_dtor (gpointer d) +{ + struct rspamd_metric_result *r = (struct rspamd_metric_result *)d; + struct rspamd_symbol_result sres; + + rspamd_set_counter_ema (&symbols_count, kh_size (r->symbols), 0.5); + + kh_foreach_value (r->symbols, sres, { + if (sres.options) { + kh_destroy (rspamd_options_hash, sres.options); + } + }); + kh_destroy (rspamd_symbols_hash, r->symbols); + kh_destroy (rspamd_symbols_group_hash, r->sym_groups); +} struct rspamd_metric_result * rspamd_create_metric_result (struct rspamd_task *task) @@ -37,22 +56,29 @@ rspamd_create_metric_result (struct rspamd_task *task) metric_res = rspamd_mempool_alloc (task->task_pool, sizeof (struct rspamd_metric_result)); - metric_res->symbols = g_hash_table_new (rspamd_str_hash, - rspamd_str_equal); - rspamd_mempool_add_destructor (task->task_pool, - (rspamd_mempool_destruct_t) g_hash_table_unref, - metric_res->symbols); - metric_res->sym_groups = g_hash_table_new (g_direct_hash, g_direct_equal); - rspamd_mempool_add_destructor (task->task_pool, - (rspamd_mempool_destruct_t) g_hash_table_unref, - metric_res->sym_groups); + metric_res->symbols = kh_init (rspamd_symbols_hash); + metric_res->sym_groups = kh_init (rspamd_symbols_group_hash); metric_res->grow_factor = 0; metric_res->score = 0; + /* Optimize allocation */ + kh_resize (rspamd_symbols_group_hash, metric_res->sym_groups, 4); + + if (symbols_count.mean > 4) { + kh_resize (rspamd_symbols_hash, metric_res->symbols, symbols_count.mean); + } + else { + kh_resize (rspamd_symbols_hash, metric_res->symbols, 4); + } + for (i = 0; i < METRIC_ACTION_MAX; i++) { metric_res->actions_limits[i] = task->cfg->actions[i].score; } + rspamd_mempool_add_destructor (task->task_pool, + rspamd_metric_result_dtor, + metric_res); + return metric_res; } @@ -91,9 +117,11 @@ insert_metric_result (struct rspamd_task *task, struct rspamd_symbol *sdef; struct rspamd_symbols_group *gr = NULL; const ucl_object_t *mobj, *sobj; - gint max_shots; + gint max_shots, ret; guint i; + khiter_t k; gboolean single = !!(flags & RSPAMD_SYMBOL_INSERT_SINGLE); + gchar *sym_cpy; metric_res = rspamd_create_metric_result (task); @@ -116,12 +144,12 @@ insert_metric_result (struct rspamd_task *task, final_score = (*sdef->weight_ptr) * weight; PTR_ARRAY_FOREACH (sdef->groups, i, gr) { - gr_score = g_hash_table_lookup (metric_res->sym_groups, gr); + k = kh_get (rspamd_symbols_group_hash, metric_res->sym_groups, gr); - if (gr_score == NULL) { - gr_score = rspamd_mempool_alloc (task->task_pool, sizeof (gdouble)); - *gr_score = 0; - g_hash_table_insert (metric_res->sym_groups, gr, gr_score); + if (k == kh_end (metric_res->sym_groups)) { + k = kh_put (rspamd_symbols_group_hash, metric_res->sym_groups, + gr, &ret); + kh_value (metric_res->sym_groups, k) = 0; } } } @@ -139,7 +167,9 @@ insert_metric_result (struct rspamd_task *task, } /* Add metric score */ - if ((s = g_hash_table_lookup (metric_res->symbols, symbol)) != NULL) { + k = kh_get (rspamd_symbols_hash, metric_res->symbols, symbol); + if (k != kh_end (metric_res->symbols)) { + s = &kh_value (metric_res->symbols, k); if (single) { max_shots = 1; } @@ -157,8 +187,16 @@ insert_metric_result (struct rspamd_task *task, } /* Now check for the duplicate options */ - if (opt && s->options && g_hash_table_lookup (s->options, opt)) { - single = TRUE; + if (opt && s->options) { + k = kh_get (rspamd_options_hash, s->options, opt); + + if (k == kh_end (s->options)) { + single = TRUE; + } + else { + s->nshots ++; + rspamd_task_add_result_option (task, s, opt); + } } else { s->nshots ++; @@ -170,7 +208,8 @@ insert_metric_result (struct rspamd_task *task, diff = final_score; } else { - if (fabs (s->score) < fabs (final_score) && signbit (s->score) == signbit (final_score)) { + if (fabs (s->score) < fabs (final_score) && + signbit (s->score) == signbit (final_score)) { /* Replace less significant weight with a more significant one */ diff = final_score - s->score; } @@ -189,27 +228,51 @@ insert_metric_result (struct rspamd_task *task, next_gf = task->cfg->grow_factor; } - diff = rspamd_check_group_score (task, symbol, gr, gr_score, diff); + if (sdef) { + PTR_ARRAY_FOREACH (sdef->groups, i, gr) { + gdouble cur_diff; + + k = kh_get (rspamd_symbols_group_hash, + metric_res->sym_groups, gr); + g_assert (k != kh_end (metric_res->sym_groups)); + gr_score = &kh_value (metric_res->sym_groups, k); + cur_diff = rspamd_check_group_score (task, symbol, gr, + gr_score, diff); + + if (isnan (cur_diff)) { + /* Limit reached, do not add result */ + diff = NAN; + break; + } else if (gr_score) { + *gr_score += cur_diff; + + if (cur_diff < diff) { + /* Reduce */ + diff = cur_diff; + } + } + } + } if (!isnan (diff)) { metric_res->score += diff; metric_res->grow_factor = next_gf; - if (gr_score) { - *gr_score += diff; - } - if (single) { s->score = final_score; - } - else { + } else { s->score += diff; } } } } else { - s = rspamd_mempool_alloc0 (task->task_pool, sizeof (struct rspamd_symbol_result)); + sym_cpy = rspamd_mempool_strdup (task->task_pool, symbol); + k = kh_put (rspamd_symbols_hash, metric_res->symbols, + sym_cpy, &ret); + g_assert (ret > 0); + s = &kh_value (metric_res->symbols, k); + memset (s, 0, sizeof (*s)); /* Handle grow factor */ if (metric_res->grow_factor && final_score > 0) { @@ -220,28 +283,46 @@ insert_metric_result (struct rspamd_task *task, next_gf = task->cfg->grow_factor; } - s->name = symbol; + s->name = sym_cpy; s->sym = sdef; s->nshots = 1; - final_score = rspamd_check_group_score (task, symbol, gr, gr_score, final_score); + if (sdef) { + /* Check group limits */ + PTR_ARRAY_FOREACH (sdef->groups, i, gr) { + gdouble cur_score; + + k = kh_get (rspamd_symbols_group_hash, metric_res->sym_groups, gr); + g_assert (k != kh_end (metric_res->sym_groups)); + gr_score = &kh_value (metric_res->sym_groups, k); + cur_score = rspamd_check_group_score (task, symbol, gr, + gr_score, final_score); + + if (isnan (cur_score)) { + /* Limit reached, do not add result */ + final_score = NAN; + break; + } else if (gr_score) { + *gr_score += cur_score; + + if (cur_score < final_score) { + /* Reduce */ + final_score = cur_score; + } + } + } + } if (!isnan (final_score)) { metric_res->score += final_score; metric_res->grow_factor = next_gf; s->score = final_score; - - if (gr_score) { - *gr_score += final_score; - } - } else { s->score = 0; } rspamd_task_add_result_option (task, s, opt); - g_hash_table_insert (metric_res->symbols, (gpointer) symbol, s); } msg_debug_task ("symbol %s, score %.2f, factor: %f", @@ -289,33 +370,39 @@ rspamd_task_add_result_option (struct rspamd_task *task, { struct rspamd_symbol_option *opt; gboolean ret = FALSE; + gchar *opt_cpy; + khiter_t k; + gint r; if (s && val) { if (s->options && !(s->sym && (s->sym->flags & RSPAMD_SYMBOL_FLAG_ONEPARAM)) && - g_hash_table_size (s->options) < task->cfg->default_max_shots) { + kh_size (s->options) < task->cfg->default_max_shots) { /* Append new options */ - if (!g_hash_table_lookup (s->options, val)) { - opt = rspamd_mempool_alloc (task->task_pool, sizeof (*opt)); - opt->option = rspamd_mempool_strdup (task->task_pool, val); + k = kh_get (rspamd_options_hash, s->options, val); + + if (k == kh_end (s->options)) { + opt = rspamd_mempool_alloc0 (task->task_pool, sizeof (*opt)); + opt_cpy = rspamd_mempool_strdup (task->task_pool, val); + k = kh_put (rspamd_options_hash, s->options, opt_cpy, &r); + + kh_value (s->options, k) = opt; + opt->option = opt_cpy; DL_APPEND (s->opts_head, opt); - g_hash_table_insert (s->options, opt->option, opt); ret = TRUE; } } else { - s->options = g_hash_table_new (rspamd_strcase_hash, - rspamd_strcase_equal); - rspamd_mempool_add_destructor (task->task_pool, - (rspamd_mempool_destruct_t)g_hash_table_unref, - s->options); - opt = rspamd_mempool_alloc (task->task_pool, sizeof (*opt)); - opt->option = rspamd_mempool_strdup (task->task_pool, val); - s->opts_head = NULL; + s->options = kh_init (rspamd_options_hash); + opt = rspamd_mempool_alloc0 (task->task_pool, sizeof (*opt)); + opt_cpy = rspamd_mempool_strdup (task->task_pool, val); + k = kh_put (rspamd_options_hash, s->options, opt_cpy, &r); + + kh_value (s->options, k) = opt; + opt->option = opt_cpy; DL_APPEND (s->opts_head, opt); - g_hash_table_insert (s->options, opt->option, opt); ret = TRUE; } } @@ -389,3 +476,36 @@ rspamd_check_action_metric (struct rspamd_task *task, struct rspamd_metric_resul return METRIC_ACTION_NOACTION; } + +struct rspamd_symbol_result* +rspamd_task_find_symbol_result (struct rspamd_task *task, const char *sym) +{ + struct rspamd_symbol_result *res = NULL; + khiter_t k; + + + if (task->result) { + k = kh_get (rspamd_symbols_hash, task->result->symbols, sym); + + if (k != kh_end (task->result->symbols)) { + res = &kh_value (task->result->symbols, k); + } + } + + return res; +} + +void +rspamd_task_symbol_result_foreach (struct rspamd_task *task, + GHFunc func, + gpointer ud) +{ + const gchar *kk; + struct rspamd_symbol_result res; + + if (func && task->result) { + kh_foreach (task->result->symbols, kk, res, { + func ((gpointer)kk, (gpointer)&res, ud); + }); + } +}
\ No newline at end of file diff --git a/src/libmime/filter.h b/src/libmime/filter.h index 798e5305f..5a7cecb68 100644 --- a/src/libmime/filter.h +++ b/src/libmime/filter.h @@ -9,6 +9,7 @@ #include "config.h" #include "symbols_cache.h" #include "task.h" +#include "khash.h" struct rspamd_task; struct rspamd_settings; @@ -19,26 +20,49 @@ struct rspamd_symbol_option { struct rspamd_symbol_option *prev, *next; }; +enum rspamd_symbol_result_flags { + RSPAMD_SYMBOL_RESULT_NORMAL = 0, + RSPAMD_SYMBOL_RESULT_IGNORED = (1 << 0) +}; + /** * Rspamd symbol */ + +KHASH_MAP_INIT_STR (rspamd_options_hash, struct rspamd_symbol_option *); struct rspamd_symbol_result { - double score; /**< symbol's score */ - GHashTable *options; /**< list of symbol's options */ + double score; /**< symbol's score */ + khash_t(rspamd_options_hash) *options; /**< list of symbol's options */ struct rspamd_symbol_option *opts_head; /**< head of linked list of options */ const gchar *name; - struct rspamd_symbol *sym; /**< symbol configuration */ + struct rspamd_symbol *sym; /**< symbol configuration */ guint nshots; + enum rspamd_symbol_result_flags flags; }; /** * Result of metric processing */ +KHASH_MAP_INIT_STR (rspamd_symbols_hash, struct rspamd_symbol_result); +#if UINTPTR_MAX <= UINT_MAX +/* 32 bit */ +#define rspamd_ptr_hash_func(key) (khint32_t)(((uintptr_t)(key))>>1) +#else +/* likely 64 bit */ +#define rspamd_ptr_hash_func(key) (khint32_t)(((uintptr_t)(key))>>3) +#endif +#define rspamd_ptr_equal_func(a, b) ((a) == (b)) +KHASH_INIT (rspamd_symbols_group_hash, + void *, + double, + 1, + rspamd_ptr_hash_func, + rspamd_ptr_equal_func); struct rspamd_metric_result { double score; /**< total score */ double grow_factor; /**< current grow factor */ - GHashTable *symbols; /**< symbols of metric */ - GHashTable *sym_groups; /**< groups of symbols */ + khash_t(rspamd_symbols_hash) *symbols; /**< symbols of metric */ + khash_t(rspamd_symbols_group_hash) *sym_groups; /**< groups of symbols */ gdouble actions_limits[METRIC_ACTION_MAX]; /**< set of actions for this metric */ }; @@ -85,6 +109,25 @@ gboolean rspamd_task_add_result_option (struct rspamd_task *task, struct rspamd_symbol_result *s, const gchar *opt); /** + * Finds symbol result + * @param task + * @param sym + * @return + */ +struct rspamd_symbol_result* rspamd_task_find_symbol_result ( + struct rspamd_task *task, const char *sym); + +/** + * Compatibility function to iterate on symbols hash + * @param task + * @param func + * @param ud + */ +void rspamd_task_symbol_result_foreach (struct rspamd_task *task, + GHFunc func, + gpointer ud); + +/** * Default consolidation function for metric, it get all symbols and multiply symbol * weight by some factor that is specified in config. Default factor is 1. * @param task worker's task that present message from user diff --git a/src/libmime/lang_detection.c b/src/libmime/lang_detection.c index 84d23ac63..8763365af 100644 --- a/src/libmime/lang_detection.c +++ b/src/libmime/lang_detection.c @@ -188,6 +188,7 @@ struct rspamd_lang_detector { UConverter *uchar_converter; gsize short_text_limit; gsize total_occurencies; /* number of all languages found */ + ref_entry_t ref; }; static void @@ -622,6 +623,32 @@ rspamd_language_detector_process_chain (struct rspamd_config *cfg, } } +static void +rspamd_language_detector_dtor (struct rspamd_lang_detector *d) +{ + if (d) { + if (d->uchar_converter) { + ucnv_close (d->uchar_converter); + } + + if (d->unicode_scripts) { + g_hash_table_unref (d->unicode_scripts); + } + + if (d->unigramms) { + kh_destroy (rspamd_unigram_hash, d->unigramms); + } + + if (d->trigramms) { + kh_destroy (rspamd_trigram_hash, d->trigramms); + } + + if (d->languages) { + g_ptr_array_free (d->languages, TRUE); + } + } +} + struct rspamd_lang_detector* rspamd_language_detector_init (struct rspamd_config *cfg) { @@ -702,6 +729,12 @@ rspamd_language_detector_init (struct rspamd_config *cfg) (gint)g_hash_table_size (ret->unicode_scripts), (gint)kh_size (ret->unigramms), (gint)kh_size (ret->trigramms)); + + REF_INIT_RETAIN (ret, rspamd_language_detector_dtor); + rspamd_mempool_add_destructor (cfg->cfg_pool, + (rspamd_mempool_destruct_t)rspamd_language_detector_unref, + ret); + end: if (gl.gl_pathc > 0) { globfree (&gl); @@ -1399,3 +1432,18 @@ rspamd_language_detector_detect (struct rspamd_task *task, return result; } + + +struct rspamd_lang_detector* +rspamd_language_detector_ref (struct rspamd_lang_detector* d) +{ + REF_RETAIN (d); + + return d; +} + +void +rspamd_language_detector_unref (struct rspamd_lang_detector* d) +{ + REF_RELEASE (d); +}
\ No newline at end of file diff --git a/src/libmime/lang_detection.h b/src/libmime/lang_detection.h index 0058801b8..2d28ec65a 100644 --- a/src/libmime/lang_detection.h +++ b/src/libmime/lang_detection.h @@ -37,6 +37,10 @@ struct rspamd_lang_detector_res { * @return */ struct rspamd_lang_detector* rspamd_language_detector_init (struct rspamd_config *cfg); + +struct rspamd_lang_detector* rspamd_language_detector_ref (struct rspamd_lang_detector* d); +void rspamd_language_detector_unref (struct rspamd_lang_detector* d); + /** * Convert string from utf8 to ucs32 * @param d diff --git a/src/libmime/message.c b/src/libmime/message.c index e5e43c5be..7c0429c3c 100644 --- a/src/libmime/message.c +++ b/src/libmime/message.c @@ -607,7 +607,7 @@ rspamd_check_gtube (struct rspamd_task *task, struct rspamd_mime_text_part *part g_assert (rspamd_multipattern_compile (gtube_matcher, NULL)); } - if (part->content && part->content->len > sizeof (gtube_pattern_reject) && + if (part->content && part->content->len >= sizeof (gtube_pattern_reject) && part->content->len <= max_check_size) { if ((ret = rspamd_multipattern_lookup (gtube_matcher, part->content->data, part->content->len, @@ -675,16 +675,23 @@ rspamd_message_process_text_part (struct rspamd_task *task, RSPAMD_FTOK_ASSIGN (&html_tok, "<!DOCTYPE html"); RSPAMD_FTOK_ASSIGN (&xhtml_tok, "<html"); - if (rspamd_lc_cmp (mime_part->parsed_data.begin, html_tok.begin, - MIN (html_tok.len, mime_part->parsed_data.len)) == 0 || - rspamd_lc_cmp (mime_part->parsed_data.begin, xhtml_tok.begin, - MIN (xhtml_tok.len, mime_part->parsed_data.len)) == 0) { - msg_info_task ("found html part pretending to be text/plain part"); + if (mime_part->parsed_data.len >= xhtml_tok.len && + rspamd_lc_cmp (mime_part->parsed_data.begin, + xhtml_tok.begin, xhtml_tok.len) == 0) { + found_html = TRUE; + } + else if (mime_part->parsed_data.len >= html_tok.len && + rspamd_lc_cmp (mime_part->parsed_data.begin, + html_tok.begin, html_tok.len) == 0) { found_html = TRUE; } else { found_txt = TRUE; } + + if (found_html) { + msg_info_task ("found html part pretending to be text/plain part"); + } } } else { @@ -828,9 +835,13 @@ rspamd_message_process_text_part (struct rspamd_task *task, task->result = mres; task->pre_result.action = act; task->pre_result.str = "Gtube pattern"; - ucl_object_insert_key (task->messages, - ucl_object_fromstring ("Gtube pattern"), "smtp_message", 0, - false); + + if (ucl_object_lookup (task->messages, "smtp_message") == NULL) { + ucl_object_replace_key (task->messages, + ucl_object_fromstring ("Gtube pattern"), "smtp_message", 0, + false); + } + rspamd_task_insert_result (task, GTUBE_SYMBOL, 0, NULL); return; diff --git a/src/libmime/mime_encoding.c b/src/libmime/mime_encoding.c index 7a6d4a3cb..c316b264c 100644 --- a/src/libmime/mime_encoding.c +++ b/src/libmime/mime_encoding.c @@ -82,7 +82,7 @@ rspamd_mime_get_converter_cached (const gchar *enc, UErrorCode *err) if (conv != NULL) { ucnv_setToUCallBack (conv, UCNV_TO_U_CALLBACK_SUBSTITUTE, - UCNV_SUB_STOP_ON_ILLEGAL, + NULL, NULL, NULL, err); diff --git a/src/libmime/mime_expressions.c b/src/libmime/mime_expressions.c index 329f80f95..f9bfdc1bf 100644 --- a/src/libmime/mime_expressions.c +++ b/src/libmime/mime_expressions.c @@ -1407,7 +1407,7 @@ match_smtp_data (struct rspamd_task *task, const gchar *what, gsize len) { rspamd_regexp_t *re; - gint r; + gint r = 0; if (arg->type == EXPRESSION_ARGUMENT_REGEXP) { /* This is a regexp */ @@ -1418,7 +1418,9 @@ match_smtp_data (struct rspamd_task *task, } - r = rspamd_regexp_search (re, what, len, NULL, NULL, FALSE, NULL); + if (len > 0) { + r = rspamd_regexp_search (re, what, len, NULL, NULL, FALSE, NULL); + } return r; } @@ -1598,9 +1600,12 @@ rspamd_content_type_compare_param (struct rspamd_task * task, DL_FOREACH (found, cur) { if (arg_pattern->type == EXPRESSION_ARGUMENT_REGEXP) { re = arg_pattern->data; - r = rspamd_regexp_search (re, - cur->value.begin, cur->value.len, - NULL, NULL, FALSE, NULL); + + if (cur->value.len > 0) { + r = rspamd_regexp_search (re, + cur->value.begin, cur->value.len, + NULL, NULL, FALSE, NULL); + } if (r) { return TRUE; @@ -1694,7 +1699,7 @@ rspamd_content_type_check (struct rspamd_task *task, rspamd_regexp_t *re; struct expression_argument *arg1, *arg_pattern; struct rspamd_content_type *ct; - gint r; + gint r = 0; guint i; gboolean recursive = FALSE; struct rspamd_mime_part *cur_part; @@ -1736,8 +1741,11 @@ rspamd_content_type_check (struct rspamd_task *task, if (arg_pattern->type == EXPRESSION_ARGUMENT_REGEXP) { re = arg_pattern->data; - r = rspamd_regexp_search (re, param_data->begin, param_data->len, - NULL, NULL, FALSE, NULL); + + if (param_data->len > 0) { + r = rspamd_regexp_search (re, param_data->begin, param_data->len, + NULL, NULL, FALSE, NULL); + } if (r) { return TRUE; @@ -1792,8 +1800,11 @@ compare_subtype (struct rspamd_task *task, struct rspamd_content_type *ct, } if (subtype->type == EXPRESSION_ARGUMENT_REGEXP) { re = subtype->data; - r = rspamd_regexp_search (re, ct->subtype.begin, ct->subtype.len, - NULL, NULL, FALSE, NULL); + + if (ct->subtype.len > 0) { + r = rspamd_regexp_search (re, ct->subtype.begin, ct->subtype.len, + NULL, NULL, FALSE, NULL); + } } else { srch.begin = subtype->data; @@ -1837,7 +1848,7 @@ common_has_content_part (struct rspamd_task * task, struct rspamd_mime_part *part; struct rspamd_content_type *ct; rspamd_ftok_t srch; - gint r; + gint r = 0; guint i; for (i = 0; i < task->parts->len; i ++) { @@ -1851,8 +1862,11 @@ common_has_content_part (struct rspamd_task * task, if (param_type->type == EXPRESSION_ARGUMENT_REGEXP) { re = param_type->data; - r = rspamd_regexp_search (re, ct->type.begin, ct->type.len, - NULL, NULL, FALSE, NULL); + if (ct->type.len > 0) { + r = rspamd_regexp_search (re, ct->type.begin, ct->type.len, + NULL, NULL, FALSE, NULL); + } + /* Also check subtype and length of the part */ if (r && param_subtype) { r = compare_len (part, min_len, max_len) && diff --git a/src/libmime/mime_parser.c b/src/libmime/mime_parser.c index d60a3fea2..0365a02b2 100644 --- a/src/libmime/mime_parser.c +++ b/src/libmime/mime_parser.c @@ -1077,26 +1077,22 @@ rspamd_mime_parse_message (struct rspamd_task *task, * Exim somehow uses mailbox format for messages being scanned: * From x@x.com Fri May 13 19:08:48 2016 * - * So we check if a task has non-http format then we check for such a line - * at the beginning to avoid errors + * Need to check that for all inputs due to proxy */ - if (!(task->flags & RSPAMD_TASK_FLAG_JSON) || (task->flags & - RSPAMD_TASK_FLAG_LOCAL_CLIENT)) { - if (len > sizeof ("From ") - 1) { - if (memcmp (p, "From ", sizeof ("From ") - 1) == 0) { - /* Skip to CRLF */ - msg_info_task ("mailbox input detected, enable workaround"); - p += sizeof ("From ") - 1; - len -= sizeof ("From ") - 1; - - while (len > 0 && *p != '\n') { - p ++; - len --; - } - while (len > 0 && g_ascii_isspace (*p)) { - p ++; - len --; - } + if (len > sizeof ("From ") - 1) { + if (memcmp (p, "From ", sizeof ("From ") - 1) == 0) { + /* Skip to CRLF */ + msg_info_task ("mailbox input detected, enable workaround"); + p += sizeof ("From ") - 1; + len -= sizeof ("From ") - 1; + + while (len > 0 && *p != '\n') { + p ++; + len --; + } + while (len > 0 && g_ascii_isspace (*p)) { + p ++; + len --; } } } diff --git a/src/libserver/CMakeLists.txt b/src/libserver/CMakeLists.txt index 03d11acb6..ac2c123dc 100644 --- a/src/libserver/CMakeLists.txt +++ b/src/libserver/CMakeLists.txt @@ -21,10 +21,8 @@ SET(LIBRSPAMDSERVERSRC ${CMAKE_CURRENT_SOURCE_DIR}/url.c ${CMAKE_CURRENT_SOURCE_DIR}/worker_util.c) -IF (ENABLE_HIREDIS MATCHES "ON") - LIST(APPEND LIBRSPAMDSERVERSRC "${CMAKE_CURRENT_SOURCE_DIR}/fuzzy_backend_redis.c") - LIST(APPEND LIBRSPAMDSERVERSRC "${CMAKE_CURRENT_SOURCE_DIR}/redis_pool.c") -ENDIF () +LIST(APPEND LIBRSPAMDSERVERSRC "${CMAKE_CURRENT_SOURCE_DIR}/fuzzy_backend_redis.c") +LIST(APPEND LIBRSPAMDSERVERSRC "${CMAKE_CURRENT_SOURCE_DIR}/redis_pool.c") # Librspamd-server SET(RSPAMD_SERVER ${LIBRSPAMDSERVERSRC} PARENT_SCOPE) diff --git a/src/libserver/cfg_file.h b/src/libserver/cfg_file.h index 777340e16..75b404530 100644 --- a/src/libserver/cfg_file.h +++ b/src/libserver/cfg_file.h @@ -352,7 +352,7 @@ struct rspamd_config { ucl_object_t *rcl_obj; /**< rcl object */ ucl_object_t *config_comments; /**< comments saved from the config */ ucl_object_t *doc_strings; /**< documentation strings for config options */ - GHashTable * c_modules; /**< hash of c modules indexed by module name */ + GPtrArray *c_modules; /**< list of C modules */ GHashTable * composite_symbols; /**< hash of composite symbols indexed by its name */ GList *classifiers; /**< list of all classifiers defined */ GList *statfiles; /**< list of all statfiles in config file order */ @@ -368,7 +368,8 @@ struct rspamd_config { GList *maps; /**< maps active */ gdouble map_timeout; /**< maps watch timeout */ - gdouble map_file_watch_multiplier; /**< multiplier for watch timeout when maps are files */ + gdouble map_file_watch_multiplier; /**< multiplier for watch timeout when maps are files */ + gchar *maps_cache_dir; /**< where to save HTTP cached data */ gdouble monitored_interval; /**< interval between monitored checks */ gboolean disable_monitored; /**< disable monitoring completely */ @@ -487,10 +488,15 @@ enum rspamd_post_load_options { RSPAMD_CONFIG_INIT_LIBS = 1 << 1, RSPAMD_CONFIG_INIT_SYMCACHE = 1 << 2, RSPAMD_CONFIG_INIT_VALIDATE = 1 << 3, - RSPAMD_CONFIG_INIT_NO_TLD = 1 << 4 + RSPAMD_CONFIG_INIT_NO_TLD = 1 << 4, + RSPAMD_CONFIG_INIT_PRELOAD_MAPS = 1 << 5, }; -#define RSPAMD_CONFIG_LOAD_ALL (RSPAMD_CONFIG_INIT_URL|RSPAMD_CONFIG_INIT_LIBS|RSPAMD_CONFIG_INIT_SYMCACHE|RSPAMD_CONFIG_INIT_VALIDATE) +#define RSPAMD_CONFIG_LOAD_ALL (RSPAMD_CONFIG_INIT_URL| \ + RSPAMD_CONFIG_INIT_LIBS| \ + RSPAMD_CONFIG_INIT_SYMCACHE| \ + RSPAMD_CONFIG_INIT_VALIDATE| \ + RSPAMD_CONFIG_INIT_PRELOAD_MAPS) /** * Do post load actions for config diff --git a/src/libserver/cfg_rcl.c b/src/libserver/cfg_rcl.c index 20d51b31c..3be0a655c 100644 --- a/src/libserver/cfg_rcl.c +++ b/src/libserver/cfg_rcl.c @@ -213,7 +213,8 @@ rspamd_rcl_logging_handler (rspamd_mempool_t *pool, const ucl_object_t *obj, else if (g_ascii_strcasecmp (log_level, "info") == 0) { cfg->log_level = G_LOG_LEVEL_INFO | G_LOG_LEVEL_MESSAGE; } - else if (g_ascii_strcasecmp (log_level, "message") == 0) { + else if (g_ascii_strcasecmp (log_level, "message") == 0 || + g_ascii_strcasecmp (log_level, "notice") == 0) { cfg->log_level = G_LOG_LEVEL_MESSAGE; } else if (g_ascii_strcasecmp (log_level, "silent") == 0) { @@ -1688,6 +1689,12 @@ rspamd_rcl_config_init (struct rspamd_config *cfg, GHashTable *skip_sections) 0, "Multiplier for map watch interval when map is file"); rspamd_rcl_add_default_handler (sub, + "maps_cache_dir", + rspamd_rcl_parse_struct_string, + G_STRUCT_OFFSET (struct rspamd_config, maps_cache_dir), + 0, + "Directory to save maps cached data (default: $DBDIR)"); + rspamd_rcl_add_default_handler (sub, "monitoring_watch_interval", rspamd_rcl_parse_struct_time, G_STRUCT_OFFSET (struct rspamd_config, monitored_interval), @@ -3449,9 +3456,11 @@ rspamd_config_parse_ucl (struct rspamd_config *cfg, const gchar *filename, } if (!ucl_parser_add_chunk (parser, data, st.st_size)) { - msg_err_config_forced ("ucl parser error: %s", ucl_parser_get_error (parser)); + g_set_error (err, cfg_rcl_error_quark (), errno, + "ucl parser error: %s", ucl_parser_get_error (parser)); ucl_parser_free (parser); munmap (data, st.st_size); + return FALSE; } @@ -3517,7 +3526,11 @@ rspamd_config_read (struct rspamd_config *cfg, const gchar *filename, if (!rspamd_rcl_parse (top, cfg, cfg, cfg->cfg_pool, cfg->rcl_obj, &err)) { msg_err_config ("rcl parse error: %e", err); - g_error_free (err); + + if (err) { + g_error_free (err); + } + return FALSE; } diff --git a/src/libserver/cfg_utils.c b/src/libserver/cfg_utils.c index e58232a00..b7b9dfdee 100644 --- a/src/libserver/cfg_utils.c +++ b/src/libserver/cfg_utils.c @@ -56,6 +56,7 @@ static gchar * rspamd_ucl_read_cb (gchar * chunk, struct map_cb_data *data, gboolean final); static void rspamd_ucl_fin_cb (struct map_cb_data *data); +static void rspamd_ucl_dtor_cb (struct map_cb_data *data); guint rspamd_config_log_id = (guint)-1; RSPAMD_CONSTRUCTOR(rspamd_config_log_init) @@ -133,7 +134,6 @@ rspamd_config_new (enum rspamd_config_init_flags flags) cfg->max_diff = 20480; rspamd_config_init_metric (cfg); - cfg->c_modules = g_hash_table_new (rspamd_str_hash, rspamd_str_equal); cfg->composite_symbols = g_hash_table_new (rspamd_str_hash, rspamd_str_equal); cfg->classifiers_symbols = g_hash_table_new (rspamd_str_hash, @@ -197,6 +197,8 @@ rspamd_config_new (enum rspamd_config_init_flags flags) #endif cfg->default_max_shots = DEFAULT_MAX_SHOTS; cfg->max_sessions_cache = DEFAULT_MAX_SESSIONS; + cfg->maps_cache_dir = rspamd_mempool_strdup (cfg->cfg_pool, RSPAMD_DBDIR); + cfg->c_modules = g_ptr_array_new (); REF_INIT_RETAIN (cfg, rspamd_config_free); @@ -237,7 +239,6 @@ rspamd_config_free (struct rspamd_config *cfg) ucl_object_unref (cfg->config_comments); ucl_object_unref (cfg->doc_strings); ucl_object_unref (cfg->neighbours); - g_hash_table_unref (cfg->c_modules); g_hash_table_remove_all (cfg->composite_symbols); g_hash_table_unref (cfg->composite_symbols); g_hash_table_remove_all (cfg->cfg_params); @@ -255,6 +256,7 @@ rspamd_config_free (struct rspamd_config *cfg) rspamd_re_cache_unref (cfg->re_cache); rspamd_upstreams_library_unref (cfg->ups_ctx); rspamd_mempool_delete (cfg->cfg_pool); + g_ptr_array_free (cfg->c_modules, TRUE); if (cfg->lua_state && cfg->own_lua_state) { lua_close (cfg->lua_state); @@ -652,6 +654,11 @@ rspamd_config_parse_log_format (struct rspamd_config *cfg) return TRUE; } +static void +rspamd_urls_config_dtor (gpointer _unused) +{ + rspamd_url_deinit (); +} /* * Perform post load actions @@ -750,6 +757,9 @@ rspamd_config_post_load (struct rspamd_config *cfg, else { rspamd_url_init (cfg->tld_file); } + + rspamd_mempool_add_destructor (cfg->cfg_pool, rspamd_urls_config_dtor, + NULL); } init_dynamic_config (cfg); @@ -841,7 +851,11 @@ rspamd_config_post_load (struct rspamd_config *cfg, ret = FALSE; } - return rspamd_symbols_cache_validate (cfg->cache, cfg, FALSE) && ret; + ret = rspamd_symbols_cache_validate (cfg->cache, cfg, FALSE) && ret; + } + + if (opts & RSPAMD_CONFIG_INIT_PRELOAD_MAPS) { + rspamd_map_preload (cfg); } return ret; @@ -1083,7 +1097,8 @@ rspamd_include_map_handler (const guchar *data, gsize len, "ucl include", rspamd_ucl_read_cb, rspamd_ucl_fin_cb, - (void **)pcbdata); + rspamd_ucl_dtor_cb, + (void **)pcbdata) != NULL; } /* @@ -1364,6 +1379,19 @@ rspamd_ucl_fin_cb (struct map_cb_data *data) } } +static void +rspamd_ucl_dtor_cb (struct map_cb_data *data) +{ + struct rspamd_ucl_map_cbdata *cbdata = data->cur_data; + + if (cbdata != NULL) { + if (cbdata->buf != NULL) { + g_string_free (cbdata->buf, TRUE); + } + g_free (cbdata); + } +} + gboolean rspamd_check_module (struct rspamd_config *cfg, module_t *mod) { @@ -1427,22 +1455,19 @@ rspamd_init_filters (struct rspamd_config *cfg, bool reconfig) { GList *cur; module_t *mod, **pmod; - struct module_ctx *mod_ctx; + guint i = 0; + struct module_ctx *mod_ctx, *cur_ctx; /* Init all compiled modules */ - if (!reconfig) { - for (pmod = cfg->compiled_modules; pmod != NULL && *pmod != NULL; pmod ++) { - mod = *pmod; - - if (rspamd_check_module (cfg, mod)) { - mod_ctx = g_malloc0 (sizeof (struct module_ctx)); - - if (mod->module_init_func (cfg, &mod_ctx) == 0) { - g_hash_table_insert (cfg->c_modules, - (gpointer) mod->name, - mod_ctx); - mod_ctx->mod = mod; - } + + for (pmod = cfg->compiled_modules; pmod != NULL && *pmod != NULL; pmod ++) { + mod = *pmod; + if (rspamd_check_module (cfg, mod)) { + if (mod->module_init_func (cfg, &mod_ctx) == 0) { + g_assert (mod_ctx != NULL); + g_ptr_array_add (cfg->c_modules, mod_ctx); + mod_ctx->mod = mod; + mod->ctx_offset = i ++; } } } @@ -1452,7 +1477,14 @@ rspamd_init_filters (struct rspamd_config *cfg, bool reconfig) while (cur) { /* Perform modules configuring */ - mod_ctx = g_hash_table_lookup (cfg->c_modules, cur->data); + mod_ctx = NULL; + PTR_ARRAY_FOREACH (cfg->c_modules, i, cur_ctx) { + if (g_ascii_strcasecmp (cur_ctx->mod->name, + (const gchar *)cur->data) == 0) { + mod_ctx = cur_ctx; + break; + } + } if (mod_ctx) { mod = mod_ctx->mod; @@ -1701,9 +1733,14 @@ rspamd_config_is_module_enabled (struct rspamd_config *cfg, GList *cur; struct rspamd_symbols_group *gr; lua_State *L = cfg->lua_state; + struct module_ctx *cur_ctx; + guint i; - if (g_hash_table_lookup (cfg->c_modules, module_name)) { - is_c = TRUE; + PTR_ARRAY_FOREACH (cfg->c_modules, i, cur_ctx) { + if (g_ascii_strcasecmp (cur_ctx->mod->name, module_name) == 0) { + is_c = TRUE; + break; + } } if (g_hash_table_lookup (cfg->explicit_modules, module_name) != NULL) { @@ -1878,6 +1915,9 @@ rspamd_config_radix_from_ucl (struct rspamd_config *cfg, const ucl_object_t *cur, *cur_elt; const gchar *str; + /* Cleanup */ + *target = NULL; + LL_FOREACH (obj, cur_elt) { type = ucl_object_type (cur_elt); @@ -1888,13 +1928,18 @@ rspamd_config_radix_from_ucl (struct rspamd_config *cfg, if (rspamd_map_is_map (str)) { if (rspamd_map_add_from_ucl (cfg, cur_elt, - description, rspamd_radix_read, rspamd_radix_fin, + description, + rspamd_radix_read, + rspamd_radix_fin, + rspamd_radix_dtor, (void **)target) == NULL) { g_set_error (err, g_quark_from_static_string ("rspamd-config"), EINVAL, "bad map definition %s for %s", str, ucl_object_key (obj)); return FALSE; } + + return TRUE; } else { /* Just a list */ @@ -1908,12 +1953,17 @@ rspamd_config_radix_from_ucl (struct rspamd_config *cfg, case UCL_OBJECT: /* Should be a map description */ if (rspamd_map_add_from_ucl (cfg, cur_elt, - description, rspamd_radix_read, rspamd_radix_fin, + description, + rspamd_radix_read, + rspamd_radix_fin, + rspamd_radix_dtor, (void **)target) == NULL) { g_set_error (err, g_quark_from_static_string ("rspamd-config"), EINVAL, "bad map object for %s", ucl_object_key (obj)); return FALSE; } + + return TRUE; break; case UCL_ARRAY: /* List of IP addresses */ @@ -1940,6 +1990,11 @@ rspamd_config_radix_from_ucl (struct rspamd_config *cfg, } } + /* Destroy on cfg cleanup */ + rspamd_mempool_add_destructor (cfg->cfg_pool, + (rspamd_mempool_destruct_t)rspamd_map_helper_destroy_radix, + *target); + return TRUE; } diff --git a/src/libserver/composites.c b/src/libserver/composites.c index f3dc7a956..8f3cb179d 100644 --- a/src/libserver/composites.c +++ b/src/libserver/composites.c @@ -57,7 +57,7 @@ enum rspamd_composite_action { }; struct symbol_remove_data { - struct rspamd_symbol_result *ms; + const gchar *sym; struct rspamd_composite *comp; GNode *parent; guint action; @@ -69,6 +69,7 @@ static rspamd_expression_atom_t * rspamd_composite_expr_parse (const gchar *line static gdouble rspamd_composite_expr_process (gpointer input, rspamd_expression_atom_t *atom); static gint rspamd_composite_expr_priority (rspamd_expression_atom_t *atom); static void rspamd_composite_expr_destroy (rspamd_expression_atom_t *atom); +static void composites_foreach_callback (gpointer key, gpointer value, void *data); const struct rspamd_atom_subr composite_expr_subr = { .parse = rspamd_composite_expr_parse, @@ -119,7 +120,7 @@ rspamd_composite_process_single_symbol (struct composites_data *cd, struct rspamd_composite *ncomp; struct rspamd_task *task = cd->task; - if ((ms = g_hash_table_lookup (cd->metric_res->symbols, sym)) == NULL) { + if ((ms = rspamd_task_find_symbol_result (cd->task, sym)) == NULL) { msg_debug_composites ("not found symbol %s in composite %s", sym, cd->composite->sym); if ((ncomp = @@ -130,27 +131,27 @@ rspamd_composite_process_single_symbol (struct composites_data *cd, sym, cd->composite->sym); if (isclr (cd->checked, ncomp->id * 2)) { + struct rspamd_composite *saved; + msg_debug_composites ("composite dependency %s for %s is not checked", sym, cd->composite->sym); /* Set checked for this symbol to avoid cyclic references */ setbit (cd->checked, cd->composite->id * 2); - rc = rspamd_process_expression (ncomp->expr, - RSPAMD_EXPRESSION_FLAG_NOOPT, cd); - clrbit (cd->checked, cd->composite->id * 2); + saved = cd->composite; /* Save the current composite */ + composites_foreach_callback ((gpointer)ncomp->sym, ncomp, cd); - if (rc != 0) { - setbit (cd->checked, ncomp->id * 2 + 1); - ms = g_hash_table_lookup (cd->metric_res->symbols, sym); - } + /* Restore state */ + cd->composite = saved; + clrbit (cd->checked, cd->composite->id * 2); - setbit (cd->checked, ncomp->id * 2); + ms = rspamd_task_find_symbol_result (cd->task, sym); } else { /* * XXX: in case of cyclic references this would return 0 */ if (isset (cd->checked, ncomp->id * 2 + 1)) { - ms = g_hash_table_lookup (cd->metric_res->symbols, sym); + ms = rspamd_task_find_symbol_result (cd->task, sym); } } } @@ -189,7 +190,7 @@ rspamd_composite_expr_process (gpointer input, rspamd_expression_atom_t *atom) if (isset (cd->checked, cd->composite->id * 2)) { /* We have already checked this composite, so just return its value */ if (isset (cd->checked, cd->composite->id * 2 + 1)) { - ms = g_hash_table_lookup (cd->metric_res->symbols, sym); + ms = rspamd_task_find_symbol_result (cd->task, sym); } if (ms) { @@ -243,7 +244,7 @@ rspamd_composite_expr_process (gpointer input, rspamd_expression_atom_t *atom) rd = g_hash_table_lookup (cd->symbols_to_remove, ms->name); nrd = rspamd_mempool_alloc (cd->task->task_pool, sizeof (*nrd)); - nrd->ms = ms; + nrd->sym = ms->name; /* By default remove symbols */ switch (cd->composite->policy) { @@ -333,7 +334,7 @@ composites_foreach_callback (gpointer key, gpointer value, void *data) clrbit (cd->checked, comp->id * 2 + 1); } else { - if (g_hash_table_lookup (cd->metric_res->symbols, key) != NULL) { + if (rspamd_task_find_symbol_result (cd->task, key) != NULL) { /* Already set, no need to check */ msg_debug_composites ("composite %s is already in metric " "in composites bitfield", cd->composite->sym); @@ -368,6 +369,7 @@ composites_remove_symbols (gpointer key, gpointer value, gpointer data) struct composites_data *cd = data; struct rspamd_task *task; struct symbol_remove_data *rd = value, *cur; + struct rspamd_symbol_result *ms; gboolean skip = FALSE, has_valid_op = FALSE, want_remove_score = TRUE, want_remove_symbol = TRUE, want_forced = FALSE; @@ -421,17 +423,20 @@ composites_remove_symbols (gpointer key, gpointer value, gpointer data) } } - if (has_valid_op) { - if (want_remove_symbol || want_forced) { - g_hash_table_remove (cd->metric_res->symbols, key); - msg_debug_composites ("remove symbol %s", key); - } + ms = rspamd_task_find_symbol_result (task, rd->sym); + + if (has_valid_op && ms && !(ms->flags & RSPAMD_SYMBOL_RESULT_IGNORED)) { if (want_remove_score || want_forced) { msg_debug_composites ("remove symbol weight for %s (was %.2f)", - key, rd->ms->score); - cd->metric_res->score -= rd->ms->score; - rd->ms->score = 0.0; + key, ms->score); + cd->metric_res->score -= ms->score; + ms->score = 0.0; + } + + if (want_remove_symbol || want_forced) { + ms->flags |= RSPAMD_SYMBOL_RESULT_IGNORED; + msg_debug_composites ("remove symbol %s", key); } } } diff --git a/src/libserver/dkim.c b/src/libserver/dkim.c index 4aaa74a3b..6d3ace1df 100644 --- a/src/libserver/dkim.c +++ b/src/libserver/dkim.c @@ -58,23 +58,6 @@ enum rspamd_dkim_param_type { DKIM_PARAM_IGNORE }; -/* Signature methods */ -enum rspamd_sign_type { - DKIM_SIGN_UNKNOWN = -2, - DKIM_SIGN_RSASHA1 = 0, - DKIM_SIGN_RSASHA256, - DKIM_SIGN_RSASHA512, - DKIM_SIGN_ECDSASHA256, - DKIM_SIGN_ECDSASHA512, - DKIM_SIGN_EDDSASHA256, -}; - -enum rspamd_dkim_key_type { - RSPAMD_DKIM_KEY_RSA = 0, - RSPAMD_DKIM_KEY_ECDSA, - RSPAMD_DKIM_KEY_EDDSA -}; - #define RSPAMD_DKIM_MAX_ARC_IDX 10 #define msg_err_dkim(...) rspamd_default_log_function (G_LOG_LEVEL_CRITICAL, \ @@ -292,6 +275,7 @@ rspamd_dkim_parse_signalg (rspamd_dkim_context_t * ctx, gsize len, GError **err) { + /* XXX: ugly size comparison, improve this code style some day */ if (len == 8) { if (memcmp (param, "rsa-sha1", len) == 0) { ctx->sig_alg = DKIM_SIGN_RSASHA1; @@ -308,7 +292,7 @@ rspamd_dkim_parse_signalg (rspamd_dkim_context_t * ctx, return TRUE; } } - else if (len == sizeof ("ecdsa256-sha256") - 1) { + else if (len == 15) { if (memcmp (param, "ecdsa256-sha256", len) == 0) { ctx->sig_alg = DKIM_SIGN_ECDSASHA256; return TRUE; @@ -318,8 +302,8 @@ rspamd_dkim_parse_signalg (rspamd_dkim_context_t * ctx, return TRUE; } } - else if (len == sizeof ("ed25519") - 1) { - if (memcmp (param, "ed25519", len) == 0) { + else if (len == 14) { + if (memcmp (param, "ed25519-sha256", len) == 0) { ctx->sig_alg = DKIM_SIGN_EDDSASHA256; return TRUE; } @@ -1176,7 +1160,8 @@ rspamd_create_dkim_context (const gchar *sig, md_alg = EVP_sha1 (); } else if (ctx->sig_alg == DKIM_SIGN_RSASHA256 || - ctx->sig_alg == DKIM_SIGN_ECDSASHA256) { + ctx->sig_alg == DKIM_SIGN_ECDSASHA256 || + ctx->sig_alg == DKIM_SIGN_EDDSASHA256) { md_alg = EVP_sha256 (); } else if (ctx->sig_alg == DKIM_SIGN_RSASHA512 || @@ -1221,8 +1206,8 @@ struct rspamd_dkim_key_cbdata { gpointer ud; }; -static rspamd_dkim_key_t * -rspamd_dkim_make_key (rspamd_dkim_context_t *ctx, const gchar *keydata, +rspamd_dkim_key_t * +rspamd_dkim_make_key (const gchar *keydata, guint keylen, enum rspamd_dkim_key_type type, GError **err) { rspamd_dkim_key_t *key = NULL; @@ -1371,9 +1356,8 @@ rspamd_dkim_sign_key_free (rspamd_dkim_sign_key_t *key) g_free (key); } -static rspamd_dkim_key_t * -rspamd_dkim_parse_key (rspamd_dkim_context_t *ctx, const gchar *txt, - gsize *keylen, GError **err) +rspamd_dkim_key_t * +rspamd_dkim_parse_key (const gchar *txt, gsize *keylen, GError **err) { const gchar *c, *p, *end, *key = NULL, *alg = "rsa"; enum { @@ -1455,6 +1439,8 @@ rspamd_dkim_parse_key (rspamd_dkim_context_t *ctx, const gchar *txt, DKIM_ERROR, DKIM_SIGERROR_KEYFAIL, "key is missing"); + + return NULL; } if (alglen == 0 || alg == NULL) { @@ -1467,20 +1453,16 @@ rspamd_dkim_parse_key (rspamd_dkim_context_t *ctx, const gchar *txt, } if (alglen == 8 && rspamd_lc_cmp (alg, "ecdsa256", alglen) == 0) { - return rspamd_dkim_make_key (ctx, c, klen, + return rspamd_dkim_make_key (c, klen, RSPAMD_DKIM_KEY_ECDSA, err); } else if (alglen == 7 && rspamd_lc_cmp (alg, "ed25519", alglen) == 0) { - return rspamd_dkim_make_key (ctx, c, klen, + return rspamd_dkim_make_key (c, klen, RSPAMD_DKIM_KEY_EDDSA, err); } else { /* We assume RSA default in all cases */ - if (alglen != 3 || rspamd_lc_cmp (alg, "rsa", alglen) != 0) { - msg_info_dkim ("invalid key algorithm: %*s", (gint)alglen, alg); - } - - return rspamd_dkim_make_key (ctx, c, klen, + return rspamd_dkim_make_key (c, klen, RSPAMD_DKIM_KEY_RSA, err); } @@ -1524,7 +1506,7 @@ rspamd_dkim_dns_cb (struct rdns_reply *reply, gpointer arg) g_error_free (err); err = NULL; } - key = rspamd_dkim_parse_key (cbdata->ctx, elt->content.txt.data, + key = rspamd_dkim_parse_key (elt->content.txt.data, &keylen, &err); if (key) { @@ -3018,3 +3000,39 @@ rspamd_dkim_sign (struct rspamd_task *task, const gchar *selector, return hdr; } + +gboolean +rspamd_dkim_match_keys (rspamd_dkim_key_t *pk, + rspamd_dkim_sign_key_t *sk, + GError **err) +{ + const BIGNUM *n1, *n2; + + if (pk == NULL || sk == NULL) { + g_set_error (err, dkim_error_quark (), DKIM_SIGERROR_KEYFAIL, + "missing public or private key"); + return FALSE; + } + + if (pk->type != RSPAMD_DKIM_KEY_RSA) { + g_set_error (err, dkim_error_quark (), DKIM_SIGERROR_KEYFAIL, + "pubkey is not RSA key"); + return FALSE; + } + +#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) + RSA_get0_key (pk->key.key_rsa, &n1, NULL, NULL); + RSA_get0_key (sk->key_rsa, &n2, NULL, NULL); +#else + n1 = pk->key.key_rsa->n; + n2 = sk->key_rsa->n; +#endif + + if (BN_cmp (n1, n2) != 0) { + g_set_error (err, dkim_error_quark (), DKIM_SIGERROR_KEYHASHMISMATCH, + "pubkey does not match private key"); + return FALSE; + } + + return TRUE; +}
\ No newline at end of file diff --git a/src/libserver/dkim.h b/src/libserver/dkim.h index 2a8b0e0b5..6ce099464 100644 --- a/src/libserver/dkim.h +++ b/src/libserver/dkim.h @@ -120,6 +120,24 @@ enum rspamd_dkim_type { RSPAMD_DKIM_ARC_SEAL }; +/* Signature methods */ +enum rspamd_sign_type { + DKIM_SIGN_UNKNOWN = -2, + DKIM_SIGN_RSASHA1 = 0, + DKIM_SIGN_RSASHA256, + DKIM_SIGN_RSASHA512, + DKIM_SIGN_ECDSASHA256, + DKIM_SIGN_ECDSASHA512, + DKIM_SIGN_EDDSASHA256, +}; + +enum rspamd_dkim_key_type { + RSPAMD_DKIM_KEY_RSA = 0, + RSPAMD_DKIM_KEY_ECDSA, + RSPAMD_DKIM_KEY_EDDSA +}; + + /* Err MUST be freed if it is not NULL, key is allocated by slice allocator */ typedef void (*dkim_key_handler_f)(rspamd_dkim_key_t *key, gsize keylen, rspamd_dkim_context_t *ctx, gpointer ud, GError *err); @@ -208,6 +226,28 @@ const gchar* rspamd_dkim_get_dns_key (rspamd_dkim_context_t *ctx); guint rspamd_dkim_key_get_ttl (rspamd_dkim_key_t *k); /** + * Create DKIM public key from a raw data + * @param keydata + * @param keylen + * @param type + * @param err + * @return + */ +rspamd_dkim_key_t * rspamd_dkim_make_key (const gchar *keydata, guint keylen, + enum rspamd_dkim_key_type type, + GError **err); + +/** + * Parse DKIM public key from a TXT record + * @param txt + * @param keylen + * @param err + * @return + */ +rspamd_dkim_key_t * rspamd_dkim_parse_key (const gchar *txt, gsize *keylen, + GError **err); + +/** * Canonocalise header using relaxed algorithm * @param hname * @param hvalue @@ -221,6 +261,17 @@ goffset rspamd_dkim_canonize_header_relaxed_str (const gchar *hname, gsize outlen); /** + * Checks public and private keys for match + * @param pk + * @param sk + * @param err + * @return + */ +gboolean rspamd_dkim_match_keys (rspamd_dkim_key_t *pk, + rspamd_dkim_sign_key_t *sk, + GError **err); + +/** * Free DKIM key * @param key */ diff --git a/src/libserver/dns.c b/src/libserver/dns.c index 9a97d8ee3..d75ad00e8 100644 --- a/src/libserver/dns.c +++ b/src/libserver/dns.c @@ -14,12 +14,14 @@ * limitations under the License. */ +#include <contrib/librdns/rdns.h> #include "config.h" #include "dns.h" #include "rspamd.h" #include "utlist.h" #include "uthash.h" #include "rdns_event.h" +#include "unix-std.h" static struct rdns_upstream_elt* rspamd_dns_select_upstream (const char *name, size_t len, void *ups_data); @@ -239,12 +241,241 @@ rspamd_dns_resolv_conf_on_server (struct rdns_resolver *resolver, int priority, unsigned int io_cnt, void *ud) { struct rspamd_dns_resolver *dns_resolver = ud; + struct rspamd_config *cfg; + rspamd_inet_addr_t *addr; + gint test_fd; + + cfg = dns_resolver->cfg; + + msg_info_config ("parsed nameserver %s from resolv.conf", name); + + /* Try to open a connection */ + if (!rspamd_parse_inet_address (&addr, name, strlen (name))) { + msg_warn_config ("cannot parse nameserver address %s", name); + + return FALSE; + } + + rspamd_inet_address_set_port (addr, port); + test_fd = rspamd_inet_address_connect (addr, SOCK_DGRAM, TRUE); + + if (test_fd == -1) { + msg_warn_config ("cannot open connection to nameserver at address %s: %s", + name, strerror (errno)); + rspamd_inet_address_free (addr); + + return FALSE; + } + + rspamd_inet_address_free (addr); + close (test_fd); return rspamd_upstreams_add_upstream (dns_resolver->ups, name, port, RSPAMD_UPSTREAM_PARSE_NAMESERVER, NULL); } +static void +rspamd_dns_resolver_config_ucl (struct rspamd_config *cfg, + struct rspamd_dns_resolver *dns_resolver, + const ucl_object_t *dns_section) +{ + const ucl_object_t *fake_replies, *cur; + ucl_object_iter_t it; + + /* Process fake replies */ + fake_replies = ucl_object_lookup_any (dns_section, "fake_records", + "fake_replies", NULL); + + if (fake_replies && ucl_object_type (fake_replies) == UCL_ARRAY) { + it = ucl_object_iterate_new (fake_replies); + + while ((cur = ucl_object_iterate_safe (it, true))) { + const ucl_object_t *type_obj, *name_obj, *code_obj, *replies_obj; + enum rdns_request_type rtype = RDNS_REQUEST_A; + enum dns_rcode rcode = RDNS_RC_NOERROR; + struct rdns_reply_entry *replies = NULL; + const gchar *name = NULL; + + if (ucl_object_type (cur) != UCL_OBJECT) { + continue; + } + + name_obj = ucl_object_lookup (cur, "name"); + if (name_obj == NULL || + (name = ucl_object_tostring (name_obj)) == NULL) { + msg_err_config ("no name for fake dns reply"); + continue; + } + + type_obj = ucl_object_lookup (cur, "type"); + if (type_obj) { + rtype = rdns_type_fromstr (ucl_object_tostring (type_obj)); + + if (rtype == RDNS_REQUEST_INVALID) { + msg_err_config ("invalid type for %s: %s", name, + ucl_object_tostring (type_obj)); + continue; + } + } + + code_obj = ucl_object_lookup_any (cur, "code", "rcode", NULL); + if (code_obj) { + rcode = rdns_rcode_fromstr (ucl_object_tostring (code_obj)); + + if (rcode == RDNS_RC_INVALID) { + msg_err_config ("invalid rcode for %s: %s", name, + ucl_object_tostring (code_obj)); + continue; + } + } + + if (rcode == RDNS_RC_NOERROR) { + /* We want replies to be set for this rcode */ + replies_obj = ucl_object_lookup (cur, "replies"); + + if (replies_obj == NULL || ucl_object_type (replies_obj) != UCL_ARRAY) { + msg_err_config ("invalid replies for fake DNS record %s", name); + continue; + } + + ucl_object_iter_t rep_it; + const ucl_object_t *rep_obj; + + rep_it = ucl_object_iterate_new (replies_obj); + + while ((rep_obj = ucl_object_iterate_safe (rep_it, true))) { + const gchar *str_rep = ucl_object_tostring (rep_obj); + struct rdns_reply_entry *rep; + gchar **svec; + + if (str_rep == NULL) { + msg_err_config ("invalid reply element for fake DNS record %s", + name); + continue; + } + + rep = calloc (1, sizeof (*rep)); + g_assert (rep != NULL); + + rep->type = rtype; + rep->ttl = 0; + + switch (rtype) { + case RDNS_REQUEST_A: + if (inet_pton (AF_INET, str_rep, &rep->content.a.addr) != 1) { + msg_err_config ("invalid A reply element for fake " + "DNS record %s: %s", + name, str_rep); + free (rep); + } + else { + DL_APPEND (replies, rep); + } + break; + case RDNS_REQUEST_NS: + rep->content.ns.name = strdup (str_rep); + DL_APPEND (replies, rep); + break; + case RDNS_REQUEST_PTR: + rep->content.ptr.name = strdup (str_rep); + DL_APPEND (replies, rep); + break; + case RDNS_REQUEST_MX: + svec = g_strsplit_set (str_rep, " :", -1); + + if (svec && svec[0] && svec[1]) { + rep->content.mx.name = strdup (svec[0]); + rep->content.mx.priority = strtoul (svec[1], NULL, 10); + DL_APPEND (replies, rep); + } + else { + msg_err_config ("invalid MX reply element for fake " + "DNS record %s: %s", + name, str_rep); + free (rep); + } + + g_strfreev (svec); + break; + case RDNS_REQUEST_TXT: + rep->content.txt.data = strdup (str_rep); + DL_APPEND (replies, rep); + break; + case RDNS_REQUEST_SOA: + svec = g_strsplit_set (str_rep, " :", -1); + + /* 7 elements */ + if (svec && svec[0] && svec[1] && svec[2] && + svec[3] && svec[4] && svec[5] && svec[6]) { + rep->content.soa.mname = strdup (svec[0]); + rep->content.soa.admin = strdup (svec[1]); + rep->content.soa.serial = strtoul (svec[2], NULL, 10); + rep->content.soa.refresh = strtol (svec[3], NULL, 10); + rep->content.soa.retry = strtol (svec[4], NULL, 10); + rep->content.soa.expire = strtol (svec[5], NULL, 10); + rep->content.soa.minimum = strtoul (svec[6], NULL, 10); + DL_APPEND (replies, rep); + } + else { + msg_err_config ("invalid MX reply element for fake " + "DNS record %s: %s", + name, str_rep); + free (rep); + } + + g_strfreev (svec); + break; + case RDNS_REQUEST_AAAA: + if (inet_pton (AF_INET6, str_rep, &rep->content.aaa.addr) != 1) { + msg_err_config ("invalid AAAA reply element for fake " + "DNS record %s: %s", + name, str_rep); + free (rep); + } + else { + DL_APPEND (replies, rep); + } + case RDNS_REQUEST_SRV: + default: + msg_err_config ("invalid or unsupported reply element " + "for fake DNS record %s: %s", + name, str_rep); + free (rep); + break; + } + } + + ucl_object_iterate_free (rep_it); + + if (replies) { + msg_info_config ("added fake record: %s", name); + rdns_resolver_set_fake_reply (dns_resolver->r, + name, rtype, rcode, replies); + } + else { + msg_warn_config ("record %s has no replies, not adding", + name); + } + } + else { + /* This entry returns some non valid code, no replies are possible */ + replies_obj = ucl_object_lookup (cur, "replies"); + + if (replies_obj) { + msg_warn_config ("replies are set for non-successful return " + "code for %s, they will be ignored", name); + } + + rdns_resolver_set_fake_reply (dns_resolver->r, + name, rtype, rcode, NULL); + } + } + + ucl_object_iterate_free (it); + } +} + struct rspamd_dns_resolver * dns_resolver_init (rspamd_logger_t *logger, struct event_base *ev_base, @@ -315,6 +546,21 @@ dns_resolver_init (rspamd_logger_t *logger, rdns_resolver_set_upstream_lib (dns_resolver->r, &rspamd_ups_ctx, dns_resolver->ups); cfg->dns_resolver = dns_resolver; + + if (cfg->rcl_obj) { + /* Configure additional options */ + const ucl_object_t *opts_section, *dns_section; + + opts_section = ucl_object_lookup (cfg->rcl_obj, "options"); + + if (opts_section) { + dns_section = ucl_object_lookup (opts_section, "dns"); + + if (dns_section) { + rspamd_dns_resolver_config_ucl (cfg, dns_resolver, dns_section); + } + } + } } rdns_resolver_set_logger (dns_resolver->r, rspamd_rnds_log_bridge, logger); @@ -376,7 +622,7 @@ rspamd_dns_upstream_fail (struct rdns_upstream_elt *elt, { struct upstream *up = elt->lib_data; - rspamd_upstream_fail (up); + rspamd_upstream_fail (up, FALSE); } static unsigned int diff --git a/src/libserver/dynamic_cfg.c b/src/libserver/dynamic_cfg.c index 121158526..33231b462 100644 --- a/src/libserver/dynamic_cfg.c +++ b/src/libserver/dynamic_cfg.c @@ -152,7 +152,6 @@ json_config_read_cb (gchar * chunk, if (data->cur_data == NULL) { jb = g_malloc0 (sizeof (*jb)); jb->cfg = pd->cfg; - jb->buf = pd->buf; data->cur_data = jb; } else { @@ -161,7 +160,7 @@ json_config_read_cb (gchar * chunk, if (jb->buf == NULL) { /* Allocate memory for buffer */ - jb->buf = g_string_sized_new (BUFSIZ); + jb->buf = g_string_sized_new (MAX (len, BUFSIZ)); } g_string_append_len (jb->buf, chunk, len); @@ -176,9 +175,13 @@ json_config_fin_cb (struct map_cb_data *data) ucl_object_t *top; struct ucl_parser *parser; - if (data->prev_data) { + if (data->cur_data && data->prev_data) { jb = data->prev_data; /* Clean prev data */ + if (jb->buf) { + g_string_free (jb->buf, TRUE); + } + g_free (jb); } @@ -187,9 +190,9 @@ json_config_fin_cb (struct map_cb_data *data) jb = data->cur_data; } else { - msg_err ("no data read"); return; } + if (jb->buf == NULL) { msg_err ("no data read"); return; @@ -218,6 +221,26 @@ json_config_fin_cb (struct map_cb_data *data) jb->cfg->current_dynamic_conf = top; } +static void +json_config_dtor_cb (struct map_cb_data *data) +{ + struct config_json_buf *jb; + + if (data->cur_data) { + jb = data->cur_data; + /* Clean prev data */ + if (jb->buf) { + g_string_free (jb->buf, TRUE); + } + + if (jb->cfg && jb->cfg->current_dynamic_conf) { + ucl_object_unref (jb->cfg->current_dynamic_conf); + } + + g_free (jb); + } +} + /** * Init dynamic configuration using map logic and specific configuration * @param cfg config file @@ -239,9 +262,17 @@ init_dynamic_config (struct rspamd_config *cfg) jb->cfg = cfg; *pjb = jb; cfg->current_dynamic_conf = ucl_object_typed_new (UCL_ARRAY); - - if (!rspamd_map_add (cfg, cfg->dynamic_conf, "Dynamic configuration map", - json_config_read_cb, json_config_fin_cb, (void **)pjb)) { + rspamd_mempool_add_destructor (cfg->cfg_pool, + (rspamd_mempool_destruct_t)g_free, + pjb); + + if (!rspamd_map_add (cfg, + cfg->dynamic_conf, + "Dynamic configuration map", + json_config_read_cb, + json_config_fin_cb, + json_config_dtor_cb, + (void **)pjb)) { msg_err ("cannot add map for configuration %s", cfg->dynamic_conf); } } diff --git a/src/libserver/events.c b/src/libserver/events.c index d609bcbb7..9b0d049d4 100644 --- a/src/libserver/events.c +++ b/src/libserver/events.c @@ -44,6 +44,9 @@ INIT_LOG_MODULE(events) +/* Average symbols count to optimize hash allocation */ +static struct rspamd_counter_data events_count; + struct rspamd_watch_stack { event_watcher_t cb; gpointer ud; @@ -63,11 +66,22 @@ struct rspamd_async_event { struct rspamd_async_watcher *w; }; +static guint rspamd_event_hash (gconstpointer a); +static gboolean rspamd_event_equal (gconstpointer a, gconstpointer b); + +/* Define **SET** of events */ +KHASH_INIT (rspamd_events_hash, + struct rspamd_async_event *, + char, + false, + rspamd_event_hash, + rspamd_event_equal); + struct rspamd_async_session { session_finalizer_t fin; event_finalizer_t restore; event_finalizer_t cleanup; - GHashTable *events; + khash_t(rspamd_events_hash) *events; void *user_data; rspamd_mempool_t *pool; struct rspamd_async_watcher *cur_watcher; @@ -90,41 +104,58 @@ static guint rspamd_event_hash (gconstpointer a) { const struct rspamd_async_event *ev = a; - rspamd_cryptobox_fast_hash_state_t st; - union { + union _pointer_fp_thunk { event_finalizer_t f; gpointer p; - } u; + }; + struct ev_storage { + union _pointer_fp_thunk p; + gpointer ud; + } st; - u.f = ev->fin; + st.p.f = ev->fin; + st.ud = ev->user_data; - rspamd_cryptobox_fast_hash_init (&st, rspamd_hash_seed ()); - rspamd_cryptobox_fast_hash_update (&st, &ev->user_data, sizeof (gpointer)); - rspamd_cryptobox_fast_hash_update (&st, &u, sizeof (u)); - - return rspamd_cryptobox_fast_hash_final (&st); + return rspamd_cryptobox_fast_hash (&st, sizeof (st), rspamd_hash_seed ()); } +static void +rspamd_session_dtor (gpointer d) +{ + struct rspamd_async_session *s = (struct rspamd_async_session *)d; + + /* Events are usually empty at this point */ + rspamd_set_counter_ema (&events_count, s->events->n_buckets, 0.5); + kh_destroy (rspamd_events_hash, s->events); +} struct rspamd_async_session * -rspamd_session_create (rspamd_mempool_t * pool, session_finalizer_t fin, - event_finalizer_t restore, event_finalizer_t cleanup, void *user_data) +rspamd_session_create (rspamd_mempool_t * pool, + session_finalizer_t fin, + event_finalizer_t restore, + event_finalizer_t cleanup, + void *user_data) { - struct rspamd_async_session *new; - - new = rspamd_mempool_alloc0 (pool, sizeof (struct rspamd_async_session)); - new->pool = pool; - new->fin = fin; - new->restore = restore; - new->cleanup = cleanup; - new->user_data = user_data; - new->events = g_hash_table_new (rspamd_event_hash, rspamd_event_equal); + struct rspamd_async_session *s; + + s = rspamd_mempool_alloc0 (pool, sizeof (struct rspamd_async_session)); + s->pool = pool; + s->fin = fin; + s->restore = restore; + s->cleanup = cleanup; + s->user_data = user_data; + s->events = kh_init (rspamd_events_hash); + + if (events_count.mean > 4) { + kh_resize (rspamd_events_hash, s->events, events_count.mean); + } + else { + kh_resize (rspamd_events_hash, s->events, 4); + } - rspamd_mempool_add_destructor (pool, - (rspamd_mempool_destruct_t) g_hash_table_destroy, - new->events); + rspamd_mempool_add_destructor (pool, rspamd_session_dtor, s); - return new; + return s; } struct rspamd_async_event * @@ -134,13 +165,11 @@ rspamd_session_add_event (struct rspamd_async_session *session, GQuark subsystem) { struct rspamd_async_event *new_event; + gint ret; if (session == NULL) { msg_err ("session is NULL"); - abort (); - - /* Not reached */ - return NULL; + g_assert_not_reached (); } new_event = rspamd_mempool_alloc (session->pool, @@ -155,7 +184,7 @@ rspamd_session_add_event (struct rspamd_async_session *session, msg_debug_session ("added event: %p, pending %d events, " "subsystem: %s, watcher: %d", user_data, - g_hash_table_size (session->events), + kh_size (session->events), g_quark_to_string (subsystem), new_event->w->id); } @@ -164,11 +193,12 @@ rspamd_session_add_event (struct rspamd_async_session *session, msg_debug_session ("added event: %p, pending %d events, " "subsystem: %s, no watcher!", user_data, - g_hash_table_size (session->events), + kh_size (session->events), g_quark_to_string (subsystem)); } - g_hash_table_insert (session->events, new_event, new_event); + kh_put (rspamd_events_hash, session->events, new_event, &ret); + g_assert (ret > 0); return new_event; } @@ -192,6 +222,7 @@ rspamd_session_remove_event (struct rspamd_async_session *session, void *ud) { struct rspamd_async_event search_ev, *found_ev; + khiter_t k; if (session == NULL) { msg_err ("session is NULL"); @@ -201,8 +232,24 @@ rspamd_session_remove_event (struct rspamd_async_session *session, /* Search for event */ search_ev.fin = fin; search_ev.user_data = ud; - found_ev = g_hash_table_lookup (session->events, &search_ev); - g_assert (found_ev != NULL); + k = kh_get (rspamd_events_hash, session->events, &search_ev); + if (k == kh_end (session->events)) { + gchar t; + + msg_err_session ("cannot find event: %p(%p)", fin, ud); + kh_foreach (session->events, found_ev, t, { + msg_err_session ("existing event %s: %p(%p)", + g_quark_to_string (found_ev->subsystem), + found_ev->fin, found_ev->user_data); + }); + + (void)t; + + g_assert_not_reached (); + } + + found_ev = kh_key (session->events, k); + kh_del (rspamd_events_hash, session->events, k); /* Remove event */ fin (ud); @@ -212,7 +259,7 @@ rspamd_session_remove_event (struct rspamd_async_session *session, msg_debug_session ("removed event: %p, subsystem: %s, " "pending %d events, watcher: %d (%d pending)", ud, g_quark_to_string (found_ev->subsystem), - g_hash_table_size (session->events), + kh_size (session->events), found_ev->w->id, found_ev->w->remain); if (found_ev->w->remain > 0) { @@ -225,34 +272,12 @@ rspamd_session_remove_event (struct rspamd_async_session *session, msg_debug_session ("removed event: %p, subsystem: %s, " "pending %d events, no watcher!", ud, g_quark_to_string (found_ev->subsystem), - g_hash_table_size (session->events)); + kh_size (session->events)); } - g_hash_table_remove (session->events, found_ev); - rspamd_session_pending (session); } -static gboolean -rspamd_session_destroy_callback (gpointer k, gpointer v, gpointer d) -{ - struct rspamd_async_event *ev = v; - struct rspamd_async_session *session = d; - - /* Call event's finalizer */ - msg_debug_session ("removed event on destroy: %p, subsystem: %s", - ev->user_data, - g_quark_to_string (ev->subsystem)); - - if (ev->fin != NULL) { - ev->fin (ev->user_data); - } - - /* We ignore watchers on session destroying */ - - return TRUE; -} - gboolean rspamd_session_destroy (struct rspamd_async_session *session) { @@ -276,14 +301,28 @@ rspamd_session_destroy (struct rspamd_async_session *session) void rspamd_session_cleanup (struct rspamd_async_session *session) { + struct rspamd_async_event *ev; + gchar t; + if (session == NULL) { msg_err ("session is NULL"); return; } - g_hash_table_foreach_remove (session->events, - rspamd_session_destroy_callback, - session); + kh_foreach (session->events, ev, t, { + /* Call event's finalizer */ + msg_debug_session ("removed event on destroy: %p, subsystem: %s", + ev->user_data, + g_quark_to_string (ev->subsystem)); + + if (ev->fin != NULL) { + ev->fin (ev->user_data); + } + }); + + (void)t; + + kh_clear (rspamd_events_hash, session->events); } gboolean @@ -291,7 +330,7 @@ rspamd_session_pending (struct rspamd_async_session *session) { gboolean ret = TRUE; - if (g_hash_table_size (session->events) == 0) { + if (kh_size (session->events) == 0) { if (session->fin != NULL) { msg_debug_session ("call fin handler, as no events are pending"); @@ -366,7 +405,7 @@ rspamd_session_events_pending (struct rspamd_async_session *session) g_assert (session != NULL); - npending = g_hash_table_size (session->events); + npending = kh_size (session->events); msg_debug_session ("pending %d events", npending); if (RSPAMD_SESSION_IS_WATCHING (session)) { diff --git a/src/libserver/fuzzy_backend.c b/src/libserver/fuzzy_backend.c index b2bf90d99..6de977ff6 100644 --- a/src/libserver/fuzzy_backend.c +++ b/src/libserver/fuzzy_backend.c @@ -19,6 +19,7 @@ #include "fuzzy_backend_sqlite.h" #include "fuzzy_backend_redis.h" #include "cfg_file.h" +#include "fuzzy_wire.h" #define DEFAULT_EXPIRE 172800L @@ -165,7 +166,7 @@ rspamd_fuzzy_backend_update_sqlite (struct rspamd_fuzzy_backend *bk, struct fuzzy_peer_cmd *io_cmd; struct rspamd_fuzzy_cmd *cmd; gpointer ptr; - guint nupdates = 0; + guint nupdates = 0, nadded = 0, ndeleted = 0, nextended = 0, nignored = 0; if (rspamd_fuzzy_backend_sqlite_prepare_update (sq, src)) { for (i = 0; i < updates->len; i ++) { @@ -182,12 +183,22 @@ rspamd_fuzzy_backend_update_sqlite (struct rspamd_fuzzy_backend *bk, if (cmd->cmd == FUZZY_WRITE) { rspamd_fuzzy_backend_sqlite_add (sq, ptr); + nadded ++; + nupdates ++; } - else { + else if (cmd->cmd == FUZZY_DEL) { rspamd_fuzzy_backend_sqlite_del (sq, ptr); + ndeleted ++; + nupdates ++; + } + else { + if (cmd->cmd == FUZZY_REFRESH) { + nextended ++; + } + else { + nignored ++; + } } - - nupdates ++; } if (rspamd_fuzzy_backend_sqlite_finish_update (sq, src, @@ -197,7 +208,7 @@ rspamd_fuzzy_backend_update_sqlite (struct rspamd_fuzzy_backend *bk, } if (cb) { - cb (success, ud); + cb (success, nadded, ndeleted, nextended, nignored, ud); } } @@ -321,6 +332,105 @@ rspamd_fuzzy_backend_check (struct rspamd_fuzzy_backend *bk, bk->subr->check (bk, cmd, cb, ud, bk->subr_ud); } +static guint +rspamd_fuzzy_digest_hash (gconstpointer key) +{ + guint ret; + + /* Distirbuted uniformly already */ + memcpy (&ret, key, sizeof (ret)); + + return ret; +} + +static gboolean +rspamd_fuzzy_digest_equal (gconstpointer v, gconstpointer v2) +{ + return memcmp (v, v2, rspamd_cryptobox_HASHBYTES) == 0; +} + +static void +rspamd_fuzzy_backend_deduplicate_queue (GArray *updates) +{ + GHashTable *seen = g_hash_table_new (rspamd_fuzzy_digest_hash, + rspamd_fuzzy_digest_equal); + struct fuzzy_peer_cmd *io_cmd, *found; + struct rspamd_fuzzy_cmd *cmd; + guchar *digest; + guint i; + + for (i = 0; i < updates->len; i ++) { + io_cmd = &g_array_index (updates, struct fuzzy_peer_cmd, i); + + if (io_cmd->is_shingle) { + cmd = &io_cmd->cmd.shingle.basic; + } + else { + cmd = &io_cmd->cmd.normal; + } + + digest = cmd->digest; + + found = g_hash_table_lookup (seen, digest); + + if (found == NULL) { + /* Add to the seen list, if not a duplicate (huh?) */ + if (cmd->cmd != FUZZY_DUP) { + g_hash_table_insert (seen, digest, io_cmd); + } + } + else { + if (found->cmd.normal.flag != cmd->flag) { + /* TODO: deal with flags better at some point */ + continue; + } + + /* Apply heuristic */ + switch (cmd->cmd) { + case FUZZY_WRITE: + if (found->cmd.normal.cmd == FUZZY_WRITE) { + /* Already seen */ + found->cmd.normal.value += cmd->value; + cmd->cmd = FUZZY_DUP; /* Ignore this one */ + } + else if (found->cmd.normal.cmd == FUZZY_REFRESH) { + /* Seen refresh command, remove it as write has higher priority */ + g_hash_table_replace (seen, digest, io_cmd); + found->cmd.normal.cmd = FUZZY_DUP; + } + else if (found->cmd.normal.cmd == FUZZY_DEL) { + /* Request delete + add, weird, but ignore add */ + cmd->cmd = FUZZY_DUP; /* Ignore this one */ + } + break; + case FUZZY_REFRESH: + if (found->cmd.normal.cmd == FUZZY_WRITE) { + /* No need to expire, handled by addition */ + cmd->cmd = FUZZY_DUP; /* Ignore this one */ + } + else if (found->cmd.normal.cmd == FUZZY_DEL) { + /* Request delete + expire, ignore expire */ + cmd->cmd = FUZZY_DUP; /* Ignore this one */ + } + else if (found->cmd.normal.cmd == FUZZY_REFRESH) { + /* Already handled */ + cmd->cmd = FUZZY_DUP; /* Ignore this one */ + } + break; + case FUZZY_DEL: + /* Delete has priority over all other commands */ + g_hash_table_replace (seen, digest, io_cmd); + found->cmd.normal.cmd = FUZZY_DUP; + break; + default: + break; + } + } + } + + g_hash_table_unref (seen); +} + void rspamd_fuzzy_backend_process_updates (struct rspamd_fuzzy_backend *bk, GArray *updates, const gchar *src, rspamd_fuzzy_update_cb cb, @@ -330,10 +440,11 @@ rspamd_fuzzy_backend_process_updates (struct rspamd_fuzzy_backend *bk, g_assert (updates != NULL); if (updates) { + rspamd_fuzzy_backend_deduplicate_queue (updates); bk->subr->update (bk, updates, src, cb, ud, bk->subr_ud); } else if (cb) { - cb (TRUE, ud); + cb (TRUE, 0, 0, 0, 0, ud); } } diff --git a/src/libserver/fuzzy_backend.h b/src/libserver/fuzzy_backend.h index 032784465..f26f3a582 100644 --- a/src/libserver/fuzzy_backend.h +++ b/src/libserver/fuzzy_backend.h @@ -27,7 +27,12 @@ struct rspamd_config; * Callbacks for fuzzy methods */ typedef void (*rspamd_fuzzy_check_cb) (struct rspamd_fuzzy_reply *rep, void *ud); -typedef void (*rspamd_fuzzy_update_cb) (gboolean success, void *ud); +typedef void (*rspamd_fuzzy_update_cb) (gboolean success, + guint nadded, + guint ndeleted, + guint nextended, + guint nignored, + void *ud); typedef void (*rspamd_fuzzy_version_cb) (guint64 rev, void *ud); typedef void (*rspamd_fuzzy_count_cb) (guint64 count, void *ud); typedef gboolean (*rspamd_fuzzy_periodic_cb) (void *ud); diff --git a/src/libserver/fuzzy_backend_redis.c b/src/libserver/fuzzy_backend_redis.c index 06f100132..7bb442a27 100644 --- a/src/libserver/fuzzy_backend_redis.c +++ b/src/libserver/fuzzy_backend_redis.c @@ -60,6 +60,13 @@ struct rspamd_fuzzy_backend_redis { ref_entry_t ref; }; +enum rspamd_fuzzy_redis_command { + RSPAMD_FUZZY_REDIS_COMMAND_COUNT, + RSPAMD_FUZZY_REDIS_COMMAND_VERSION, + RSPAMD_FUZZY_REDIS_COMMAND_UPDATES, + RSPAMD_FUZZY_REDIS_COMMAND_CHECK +}; + struct rspamd_fuzzy_redis_session { struct rspamd_fuzzy_backend_redis *backend; redisAsyncContext *ctx; @@ -69,14 +76,14 @@ struct rspamd_fuzzy_redis_session { float prob; gboolean shingles_checked; - enum { - RSPAMD_FUZZY_REDIS_COMMAND_COUNT, - RSPAMD_FUZZY_REDIS_COMMAND_VERSION, - RSPAMD_FUZZY_REDIS_COMMAND_UPDATES, - RSPAMD_FUZZY_REDIS_COMMAND_CHECK - } command; + enum rspamd_fuzzy_redis_command command; guint nargs; + guint nadded; + guint ndeleted; + guint nextended; + guint nignored; + union { rspamd_fuzzy_check_cb cb_check; rspamd_fuzzy_update_cb cb_update; @@ -467,7 +474,7 @@ rspamd_fuzzy_redis_shingles_callback (redisAsyncContext *c, gpointer r, msg_err_redis_session ("error getting shingles: %s", c->errstr); } - rspamd_upstream_fail (session->up); + rspamd_upstream_fail (session->up, FALSE); } rspamd_fuzzy_redis_session_dtor (session, FALSE); @@ -606,7 +613,7 @@ rspamd_fuzzy_redis_check_callback (redisAsyncContext *c, gpointer r, msg_err_redis_session ("error getting hashes: %s", c->errstr); } - rspamd_upstream_fail (session->up); + rspamd_upstream_fail (session->up, FALSE); } rspamd_fuzzy_redis_session_dtor (session, FALSE); @@ -674,7 +681,7 @@ rspamd_fuzzy_backend_check_redis (struct rspamd_fuzzy_backend *bk, rspamd_inet_address_get_port (addr)); if (session->ctx == NULL) { - rspamd_upstream_fail (up); + rspamd_upstream_fail (up, TRUE); rspamd_fuzzy_redis_session_dtor (session, TRUE); if (cb) { @@ -744,7 +751,7 @@ rspamd_fuzzy_redis_count_callback (redisAsyncContext *c, gpointer r, msg_err_redis_session ("error getting count: %s", c->errstr); } - rspamd_upstream_fail (session->up); + rspamd_upstream_fail (session->up, FALSE); } rspamd_fuzzy_redis_session_dtor (session, FALSE); @@ -798,7 +805,7 @@ rspamd_fuzzy_backend_count_redis (struct rspamd_fuzzy_backend *bk, rspamd_inet_address_get_port (addr)); if (session->ctx == NULL) { - rspamd_upstream_fail (up); + rspamd_upstream_fail (up, TRUE); rspamd_fuzzy_redis_session_dtor (session, TRUE); if (cb) { @@ -866,7 +873,7 @@ rspamd_fuzzy_redis_version_callback (redisAsyncContext *c, gpointer r, msg_err_redis_session ("error getting version: %s", c->errstr); } - rspamd_upstream_fail (session->up); + rspamd_upstream_fail (session->up, FALSE); } rspamd_fuzzy_redis_session_dtor (session, FALSE); @@ -921,7 +928,7 @@ rspamd_fuzzy_backend_version_redis (struct rspamd_fuzzy_backend *bk, rspamd_inet_address_get_port (addr)); if (session->ctx == NULL) { - rspamd_upstream_fail (up); + rspamd_upstream_fail (up, FALSE); rspamd_fuzzy_redis_session_dtor (session, TRUE); if (cb) { @@ -1002,7 +1009,7 @@ rspamd_fuzzy_update_append_command (struct rspamd_fuzzy_backend *bk, key = g_string_sized_new (klen); g_string_append (key, session->backend->redis_object); g_string_append_len (key, cmd->digest, sizeof (cmd->digest)); - value = g_string_sized_new (30); + value = g_string_sized_new (sizeof ("4294967296")); rspamd_printf_gstring (value, "%d", cmd->flag); session->argv[cur_shift] = g_strdup ("HSET"); session->argv_lens[cur_shift++] = sizeof ("HSET") - 1; @@ -1029,7 +1036,7 @@ rspamd_fuzzy_update_append_command (struct rspamd_fuzzy_backend *bk, key = g_string_sized_new (klen); g_string_append (key, session->backend->redis_object); g_string_append_len (key, cmd->digest, sizeof (cmd->digest)); - value = g_string_sized_new (30); + value = g_string_sized_new (sizeof ("18446744073709551616")); rspamd_printf_gstring (value, "%L", (gint64)rspamd_get_calendar_ticks ()); session->argv[cur_shift] = g_strdup ("HSETNX"); session->argv_lens[cur_shift++] = sizeof ("HSETNX") - 1; @@ -1054,7 +1061,7 @@ rspamd_fuzzy_update_append_command (struct rspamd_fuzzy_backend *bk, key = g_string_sized_new (klen); g_string_append (key, session->backend->redis_object); g_string_append_len (key, cmd->digest, sizeof (cmd->digest)); - value = g_string_sized_new (30); + value = g_string_sized_new (sizeof ("4294967296")); rspamd_printf_gstring (value, "%d", cmd->value); session->argv[cur_shift] = g_strdup ("HINCRBY"); session->argv_lens[cur_shift++] = sizeof ("HINCRBY") - 1; @@ -1079,7 +1086,7 @@ rspamd_fuzzy_update_append_command (struct rspamd_fuzzy_backend *bk, key = g_string_sized_new (klen); g_string_append (key, session->backend->redis_object); g_string_append_len (key, cmd->digest, sizeof (cmd->digest)); - value = g_string_sized_new (30); + value = g_string_sized_new (sizeof ("4294967296")); rspamd_printf_gstring (value, "%d", (gint)rspamd_fuzzy_backend_get_expire (bk)); session->argv[cur_shift] = g_strdup ("EXPIRE"); @@ -1157,6 +1164,43 @@ rspamd_fuzzy_update_append_command (struct rspamd_fuzzy_backend *bk, return FALSE; } } + else if (cmd->cmd == FUZZY_REFRESH) { + /* + * Issue refresh command by just EXPIRE command + * EXPIRE <key> <expire> + * Where <key> is <prefix> || <digest> + */ + + klen = strlen (session->backend->redis_object) + + sizeof (cmd->digest) + 1; + + /* EXPIRE */ + key = g_string_sized_new (klen); + g_string_append (key, session->backend->redis_object); + g_string_append_len (key, cmd->digest, sizeof (cmd->digest)); + value = g_string_sized_new (sizeof ("4294967296")); + rspamd_printf_gstring (value, "%d", + (gint)rspamd_fuzzy_backend_get_expire (bk)); + session->argv[cur_shift] = g_strdup ("EXPIRE"); + session->argv_lens[cur_shift++] = sizeof ("EXPIRE") - 1; + session->argv[cur_shift] = key->str; + session->argv_lens[cur_shift++] = key->len; + session->argv[cur_shift] = value->str; + session->argv_lens[cur_shift++] = value->len; + g_string_free (key, FALSE); + g_string_free (value, FALSE); + + if (redisAsyncCommandArgv (session->ctx, NULL, NULL, + 3, + (const gchar **)&session->argv[cur_shift - 3], + &session->argv_lens[cur_shift - 3]) != REDIS_OK) { + + return FALSE; + } + } + else if (cmd->cmd == FUZZY_DUP) { + /* Ignore */ + } else { g_assert_not_reached (); } @@ -1179,7 +1223,7 @@ rspamd_fuzzy_update_append_command (struct rspamd_fuzzy_backend *bk, session->backend->redis_object, i, io_cmd->cmd.shingle.sgl.hashes[i]); - value = g_string_sized_new (30); + value = g_string_sized_new (sizeof ("4294967296")); rspamd_printf_gstring (value, "%d", (gint)rspamd_fuzzy_backend_get_expire (bk)); hval = g_malloc (sizeof (io_cmd->cmd.shingle.basic.digest)); @@ -1230,6 +1274,46 @@ rspamd_fuzzy_update_append_command (struct rspamd_fuzzy_backend *bk, } } } + else if (cmd->cmd == FUZZY_REFRESH) { + klen = strlen (session->backend->redis_object) + + 64 + 1; + + for (i = 0; i < RSPAMD_SHINGLE_SIZE; i ++) { + /* + * For each command with shingles we additionally emit 32 commands: + * EXPIRE <prefix>_<number>_<value> <expire> + */ + + /* Expire */ + key = g_string_sized_new (klen); + rspamd_printf_gstring (key, "%s_%d_%uL", + session->backend->redis_object, + i, + io_cmd->cmd.shingle.sgl.hashes[i]); + value = g_string_sized_new (sizeof ("18446744073709551616")); + rspamd_printf_gstring (value, "%d", + (gint)rspamd_fuzzy_backend_get_expire (bk)); + session->argv[cur_shift] = g_strdup ("EXPIRE"); + session->argv_lens[cur_shift++] = sizeof ("EXPIRE") - 1; + session->argv[cur_shift] = key->str; + session->argv_lens[cur_shift++] = key->len; + session->argv[cur_shift] = value->str; + session->argv_lens[cur_shift++] = value->len; + g_string_free (key, FALSE); + g_string_free (value, FALSE); + + if (redisAsyncCommandArgv (session->ctx, NULL, NULL, + 3, + (const gchar **)&session->argv[cur_shift - 3], + &session->argv_lens[cur_shift - 3]) != REDIS_OK) { + + return FALSE; + } + } + } + else if (cmd->cmd == FUZZY_DUP) { + /* Ignore */ + } else { g_assert_not_reached (); } @@ -1254,25 +1338,30 @@ rspamd_fuzzy_redis_update_callback (redisAsyncContext *c, gpointer r, if (reply->type == REDIS_REPLY_ARRAY) { /* TODO: check all replies somehow */ if (session->callback.cb_update) { - session->callback.cb_update (TRUE, session->cbdata); + session->callback.cb_update (TRUE, + session->nadded, + session->ndeleted, + session->nextended, + session->nignored, + session->cbdata); } } else { if (session->callback.cb_update) { - session->callback.cb_update (FALSE, session->cbdata); + session->callback.cb_update (FALSE, 0, 0, 0, 0, session->cbdata); } } } else { if (session->callback.cb_update) { - session->callback.cb_update (FALSE, session->cbdata); + session->callback.cb_update (FALSE, 0, 0, 0, 0, session->cbdata); } if (c->errstr) { msg_err_redis_session ("error sending update to redis: %s", c->errstr); } - rspamd_upstream_fail (session->up); + rspamd_upstream_fail (session->up, FALSE); } rspamd_fuzzy_redis_session_dtor (session, FALSE); @@ -1337,6 +1426,7 @@ rspamd_fuzzy_backend_update_redis (struct rspamd_fuzzy_backend *bk, if (cmd->cmd == FUZZY_WRITE) { ncommands += 5; nargs += 17; + session->nadded ++; if (io_cmd->is_shingle) { ncommands += RSPAMD_SHINGLE_SIZE; @@ -1347,12 +1437,26 @@ rspamd_fuzzy_backend_update_redis (struct rspamd_fuzzy_backend *bk, else if (cmd->cmd == FUZZY_DEL) { ncommands += 2; nargs += 4; + session->ndeleted ++; if (io_cmd->is_shingle) { ncommands += RSPAMD_SHINGLE_SIZE; nargs += RSPAMD_SHINGLE_SIZE * 2; } } + else if (cmd->cmd == FUZZY_REFRESH) { + ncommands += 1; + nargs += 3; + session->nextended ++; + + if (io_cmd->is_shingle) { + ncommands += RSPAMD_SHINGLE_SIZE; + nargs += RSPAMD_SHINGLE_SIZE * 3; + } + } + else { + session->nignored ++; + } } /* Now we need to create a new request */ @@ -1382,11 +1486,11 @@ rspamd_fuzzy_backend_update_redis (struct rspamd_fuzzy_backend *bk, rspamd_inet_address_get_port (addr)); if (session->ctx == NULL) { - rspamd_upstream_fail (up); + rspamd_upstream_fail (up, TRUE); rspamd_fuzzy_redis_session_dtor (session, TRUE); if (cb) { - cb (FALSE, ud); + cb (FALSE, 0, 0, 0, 0, ud); } } else { @@ -1400,7 +1504,7 @@ rspamd_fuzzy_backend_update_redis (struct rspamd_fuzzy_backend *bk, session->argv_lens) != REDIS_OK) { if (cb) { - cb (FALSE, ud); + cb (FALSE, 0, 0, 0, 0, ud); } rspamd_fuzzy_redis_session_dtor (session, TRUE); @@ -1416,7 +1520,7 @@ rspamd_fuzzy_backend_update_redis (struct rspamd_fuzzy_backend *bk, if (!rspamd_fuzzy_update_append_command (bk, session, io_cmd, &cur_shift)) { if (cb) { - cb (FALSE, ud); + cb (FALSE, 0, 0, 0, 0, ud); } rspamd_fuzzy_redis_session_dtor (session, TRUE); @@ -1439,7 +1543,7 @@ rspamd_fuzzy_backend_update_redis (struct rspamd_fuzzy_backend *bk, &session->argv_lens[cur_shift - 2]) != REDIS_OK) { if (cb) { - cb (FALSE, ud); + cb (FALSE, 0, 0, 0, 0, ud); } rspamd_fuzzy_redis_session_dtor (session, TRUE); @@ -1457,7 +1561,7 @@ rspamd_fuzzy_backend_update_redis (struct rspamd_fuzzy_backend *bk, &session->argv_lens[cur_shift]) != REDIS_OK) { if (cb) { - cb (FALSE, ud); + cb (FALSE, 0, 0, 0, 0, ud); } rspamd_fuzzy_redis_session_dtor (session, TRUE); diff --git a/src/libserver/fuzzy_wire.h b/src/libserver/fuzzy_wire.h index ef9a7e4e3..4a1dc3ed4 100644 --- a/src/libserver/fuzzy_wire.h +++ b/src/libserver/fuzzy_wire.h @@ -14,7 +14,10 @@ #define FUZZY_WRITE 1 #define FUZZY_DEL 2 #define FUZZY_STAT 3 - +#define FUZZY_CLIENT_MAX 3 +/* Internal commands */ +#define FUZZY_REFRESH 100 /* Update expire */ +#define FUZZY_DUP 101 /* Skip duplicate in update queue */ /** * The epoch of the fuzzy client diff --git a/src/libserver/html.c b/src/libserver/html.c index 1ee012388..dc41bee80 100644 --- a/src/libserver/html.c +++ b/src/libserver/html.c @@ -1135,7 +1135,8 @@ rspamd_html_parse_tag_component (rspamd_mempool_t *pool, NEW_COMPONENT (RSPAMD_HTML_COMPONENT_HREF); } } - else if (tag->id == Tag_IMG) { + + if (tag->id == Tag_IMG) { /* Check width and height if presented */ if (len == 5 && g_ascii_strncasecmp (p, "width", len) == 0) { NEW_COMPONENT (RSPAMD_HTML_COMPONENT_WIDTH); @@ -1147,6 +1148,29 @@ rspamd_html_parse_tag_component (rspamd_mempool_t *pool, NEW_COMPONENT (RSPAMD_HTML_COMPONENT_STYLE); } } + else if (tag->id == Tag_FONT) { + if (len == 5){ + if (g_ascii_strncasecmp (p, "color", len) == 0) { + NEW_COMPONENT (RSPAMD_HTML_COMPONENT_COLOR); + } + else if (g_ascii_strncasecmp (p, "style", len) == 0) { + NEW_COMPONENT (RSPAMD_HTML_COMPONENT_STYLE); + } + else if (g_ascii_strncasecmp (p, "class", len) == 0) { + NEW_COMPONENT (RSPAMD_HTML_COMPONENT_CLASS); + } + } + else if (len == 7) { + if (g_ascii_strncasecmp (p, "bgcolor", len) == 0) { + NEW_COMPONENT (RSPAMD_HTML_COMPONENT_BGCOLOR); + } + } + else if (len == 4) { + if (g_ascii_strncasecmp (p, "size", len) == 0) { + NEW_COMPONENT (RSPAMD_HTML_COMPONENT_SIZE); + } + } + } else if (tag->flags & FL_BLOCK) { if (len == 5){ if (g_ascii_strncasecmp (p, "color", len) == 0) { @@ -1490,7 +1514,7 @@ rspamd_html_process_url (rspamd_mempool_t *pool, const gchar *start, guint len, /* Strip spaces from the url */ /* Head spaces */ - while ( p < start + len && g_ascii_isspace (*p)) { + while (p < start + len && g_ascii_isspace (*p)) { p ++; start ++; len --; @@ -1605,11 +1629,14 @@ rspamd_html_process_url (rspamd_mempool_t *pool, const gchar *start, guint len, } static struct rspamd_url * -rspamd_html_process_url_tag (rspamd_mempool_t *pool, struct html_tag *tag) +rspamd_html_process_url_tag (rspamd_mempool_t *pool, struct html_tag *tag, + struct html_content *hc) { struct html_tag_component *comp; GList *cur; struct rspamd_url *url; + const gchar *start; + gsize len; cur = tag->params->head; @@ -1617,7 +1644,54 @@ rspamd_html_process_url_tag (rspamd_mempool_t *pool, struct html_tag *tag) comp = cur->data; if (comp->type == RSPAMD_HTML_COMPONENT_HREF && comp->len > 0) { - url = rspamd_html_process_url (pool, comp->start, comp->len, comp); + start = comp->start; + len = comp->len; + + /* Check base url */ + if (hc && hc->base_url && comp->len > 2) { + /* + * Relative url canot start from the following: + * schema:// + * slash + */ + gchar *buf; + gsize orig_len; + + if (rspamd_substring_search (start, len, "://", 3) == -1) { + /* Assume relative url */ + + gboolean need_slash = FALSE; + + orig_len = len; + len += hc->base_url->urllen; + + if (hc->base_url->string[hc->base_url->urllen - 1] != '/') { + need_slash = TRUE; + len ++; + } + + buf = rspamd_mempool_alloc (pool, len + 1); + rspamd_snprintf (buf, len + 1, "%*s%s%*s", + hc->base_url->urllen, hc->base_url->string, + need_slash ? "/" : "", + (gint)orig_len, start); + start = buf; + } + else if (start[0] == '/' && start[1] != '/') { + /* Relative to the hostname */ + orig_len = len; + len += hc->base_url->hostlen + hc->base_url->protocollen + + 3 /* for :// */; + buf = rspamd_mempool_alloc (pool, len + 1); + rspamd_snprintf (buf, len + 1, "%*s://%*s/%*s", + hc->base_url->protocollen, hc->base_url->string, + hc->base_url->hostlen, hc->base_url->host, + (gint)orig_len, start); + start = buf; + } + } + + url = rspamd_html_process_url (pool, start, len, comp); if (url && tag->extra == NULL) { tag->extra = url; @@ -1819,6 +1893,7 @@ rspamd_html_process_color (const gchar *line, guint len, struct html_color *cl) p ++; rspamd_strlcpy (hexbuf, p, MIN ((gint)sizeof(hexbuf), end - p + 1)); cl->d.val = strtoul (hexbuf, NULL, 16); + cl->d.comp.alpha = 255; cl->valid = TRUE; } else if (len > 4 && rspamd_lc_cmp (p, "rgb", 3) == 0) { @@ -1828,9 +1903,10 @@ rspamd_html_process_color (const gchar *line, guint len, struct html_color *cl) num1, num2, num3, + num4, skip_spaces } state = skip_spaces, next_state = obrace; - gulong r = 0, g = 0, b = 0; + gulong r = 0, g = 0, b = 0, opacity = 255; const gchar *c; gboolean valid = FALSE; @@ -1899,6 +1975,40 @@ rspamd_html_process_color (const gchar *line, guint len, struct html_color *cl) } valid = TRUE; + p ++; + state = skip_spaces; + next_state = num4; + } + else if (*p == ')') { + if (!rspamd_strtoul (c, p - c, &b)) { + goto stop; + } + + valid = TRUE; + goto stop; + } + else if (!g_ascii_isdigit (*p)) { + goto stop; + } + else { + p ++; + } + break; + case num4: + if (*p == ',') { + if (!rspamd_strtoul (c, p - c, &opacity)) { + goto stop; + } + + valid = TRUE; + goto stop; + } + else if (*p == ')') { + if (!rspamd_strtoul (c, p - c, &opacity)) { + goto stop; + } + + valid = TRUE; goto stop; } else if (!g_ascii_isdigit (*p)) { @@ -1923,7 +2033,10 @@ rspamd_html_process_color (const gchar *line, guint len, struct html_color *cl) stop: if (valid) { - cl->d.val = b + (g << 8) + (r << 16); + cl->d.comp.r = r; + cl->d.comp.g = g; + cl->d.comp.b = b; + cl->d.comp.alpha = opacity; cl->valid = TRUE; } } @@ -1936,8 +2049,185 @@ rspamd_html_process_color (const gchar *line, guint len, struct html_color *cl) if (el != NULL) { memcpy (cl, el, sizeof (*cl)); + cl->d.comp.alpha = 255; /* Non transparent */ + } + } +} + +/* + * Target is used for in and out if this function returns TRUE + */ +static gboolean +rspamd_html_process_css_size (const gchar *suffix, gsize len, + gdouble *tgt) +{ + gdouble sz = *tgt; + gboolean ret = FALSE; + + if (len >= 2) { + if (memcmp (suffix, "px", 2) == 0) { + sz = (guint) sz; /* Round to number */ + ret = TRUE; + } + else if (memcmp (suffix, "em", 2) == 0) { + /* EM is 16 px, so multiply and round */ + sz = (guint) (sz * 16.0); + ret = TRUE; + } + else if (len >= 3 && memcmp (suffix, "rem", 3) == 0) { + /* equal to EM in our case */ + sz = (guint) (sz * 16.0); + ret = TRUE; + } + else if (memcmp (suffix, "ex", 2) == 0) { + /* + * Represents the x-height of the element's font. + * On fonts with the "x" letter, this is generally the height + * of lowercase letters in the font; 1ex = 0.5em in many fonts. + */ + sz = (guint) (sz * 8.0); + ret = TRUE; + } + else if (memcmp (suffix, "vw", 2) == 0) { + /* + * Vewport width in percentages: + * we assume 1% of viewport width as 8px + */ + sz = (guint) (sz * 8.0); + ret = TRUE; + } + else if (memcmp (suffix, "vh", 2) == 0) { + /* + * Vewport height in percentages + * we assume 1% of viewport width as 6px + */ + sz = (guint) (sz * 6.0); + ret = TRUE; } + else if (len >= 4 && memcmp (suffix, "vmax", 4) == 0) { + /* + * Vewport width in percentages + * we assume 1% of viewport width as 6px + */ + sz = (guint) (sz * 8.0); + ret = TRUE; + } + else if (len >= 4 && memcmp (suffix, "vmin", 4) == 0) { + /* + * Vewport height in percentages + * we assume 1% of viewport width as 6px + */ + sz = (guint) (sz * 6.0); + ret = TRUE; + } + else if (memcmp (suffix, "pt", 2) == 0) { + sz = (guint) (sz * 96.0 / 72.0); /* One point. 1pt = 1/72nd of 1in */ + ret = TRUE; + } + else if (memcmp (suffix, "cm", 2) == 0) { + sz = (guint) (sz * 96.0 / 2.54); /* 96px/2.54 */ + ret = TRUE; + } + else if (memcmp (suffix, "mm", 2) == 0) { + sz = (guint) (sz * 9.6 / 2.54); /* 9.6px/2.54 */ + ret = TRUE; + } + else if (memcmp (suffix, "in", 2) == 0) { + sz = (guint) (sz * 96.0); /* 96px */ + ret = TRUE; + } + else if (memcmp (suffix, "pc", 2) == 0) { + sz = (guint) (sz * 96.0 / 6.0); /* 1pc = 12pt = 1/6th of 1in. */ + ret = TRUE; + } + } + else if (suffix[0] == '%') { + /* Percentages from 16 px */ + sz = (guint)(sz / 100.0 * 16.0); + ret = TRUE; } + + if (ret) { + *tgt = sz; + } + + return ret; +} + +static void +rspamd_html_process_font_size (const gchar *line, guint len, guint *fs, + gboolean is_css) +{ + const gchar *p = line, *end = line + len; + gchar *err = NULL, numbuf[64]; + gdouble sz = 0; + gboolean failsafe = FALSE; + + while (p < end && g_ascii_isspace (*p)) { + p ++; + len --; + } + + if (g_ascii_isdigit (*p)) { + rspamd_strlcpy (numbuf, p, MIN (sizeof (numbuf), len + 1)); + sz = strtod (numbuf, &err); + + /* Now check leftover */ + if (sz < 0) { + sz = 0; + } + } + else { + /* Ignore the rest */ + failsafe = TRUE; + sz = is_css ? 16 : 1; + /* TODO: add textual fonts descriptions */ + } + + if (err && *err != '\0') { + const gchar *e = err; + gsize slen; + + /* Skip spaces */ + while (*e && g_ascii_isspace (*e)) { + e ++; + } + + /* Lowercase */ + slen = strlen (e); + rspamd_str_lc ((gchar *)e, slen); + + if (!rspamd_html_process_css_size (e, slen, &sz)) { + failsafe = TRUE; + } + } + else { + /* Failsafe naked number */ + failsafe = TRUE; + } + + if (failsafe) { + if (is_css) { + /* + * In css mode we usually ignore sizes, but let's treat + * small sizes specially + */ + if (sz < 1) { + sz = 0; + } else { + sz = 16; /* Ignore */ + } + } else { + /* In non-css mode we have to check legacy size */ + sz = sz >= 1 ? sz * 16 : 16; + } + } + + if (sz > 32) { + sz = 32; + } + + *fs = sz; } static void @@ -1952,6 +2242,7 @@ rspamd_html_process_style (rspamd_mempool_t *pool, struct html_block *bl, skip_spaces, } state = skip_spaces, next_state = read_key; guint klen = 0; + gdouble opacity = 1.0; p = style; c = p; @@ -2009,6 +2300,38 @@ rspamd_html_process_style (rspamd_mempool_t *pool, struct html_block *bl, msg_debug_html ("tag is not visible"); } } + else if (klen == 9 && + g_ascii_strncasecmp (key, "font-size", 9) == 0) { + rspamd_html_process_font_size (c, p - c, + &bl->font_size, TRUE); + msg_debug_html ("got font size: %u", bl->font_size); + } + else if (klen == 7 && + g_ascii_strncasecmp (key, "opacity", 7) == 0) { + gchar numbuf[64]; + + rspamd_strlcpy (numbuf, c, + MIN (sizeof (numbuf), p - c + 1)); + opacity = strtod (numbuf, NULL); + + if (opacity > 1) { + opacity = 1; + } + else if (opacity < 0) { + opacity = 0; + } + + bl->font_color.d.comp.alpha = (guint8)(opacity * 255.0); + } + else if (klen == 10 && + g_ascii_strncasecmp (key, "visibility", 10) == 0) { + if (p - c >= 6 && rspamd_substring_search_caseless (c, + p - c, + "hidden", 6) != -1) { + bl->visible = FALSE; + msg_debug_html ("tag is not visible"); + } + } } key = NULL; @@ -2039,94 +2362,69 @@ rspamd_html_process_block_tag (rspamd_mempool_t *pool, struct html_tag *tag, struct html_content *hc) { struct html_tag_component *comp; - struct html_block *bl, *bl_parent; + struct html_block *bl; rspamd_ftok_t fstr; GList *cur; - GNode *parent; - struct html_tag *parent_tag; cur = tag->params->head; bl = rspamd_mempool_alloc0 (pool, sizeof (*bl)); bl->tag = tag; bl->visible = TRUE; + bl->font_size = (guint)-1; + bl->font_color.d.comp.alpha = 255; while (cur) { comp = cur->data; - if (comp->type == RSPAMD_HTML_COMPONENT_COLOR && comp->len > 0) { - fstr.begin = (gchar *)comp->start; - fstr.len = comp->len; - rspamd_html_process_color (comp->start, comp->len, &bl->font_color); - msg_debug_html ("got color: %xd", bl->font_color.d.val); - } - else if (comp->type == RSPAMD_HTML_COMPONENT_BGCOLOR && comp->len > 0) { - fstr.begin = (gchar *)comp->start; - fstr.len = comp->len; - rspamd_html_process_color (comp->start, comp->len, &bl->background_color); - msg_debug_html ("got color: %xd", bl->font_color.d.val); - - if (tag->id == Tag_BODY) { - /* Set global background color */ - memcpy (&hc->bgcolor, &bl->background_color, sizeof (hc->bgcolor)); - } - } - else if (comp->type == RSPAMD_HTML_COMPONENT_STYLE && comp->len > 0) { - bl->style.len = comp->len; - bl->style.start = comp->start; - msg_debug_html ("got style: %*s", (gint)bl->style.len, bl->style.start); - rspamd_html_process_style (pool, bl, hc, comp->start, comp->len); - } - else if (comp->type == RSPAMD_HTML_COMPONENT_CLASS && comp->len > 0) { - fstr.begin = (gchar *)comp->start; - fstr.len = comp->len; - bl->class = rspamd_mempool_ftokdup (pool, &fstr); - msg_debug_html ("got class: %s", bl->class); - } - - cur = g_list_next (cur); - } - - if (!bl->background_color.valid) { - /* Try to propagate background color from parent nodes */ - for (parent = tag->parent; parent != NULL; parent = parent->parent) { - parent_tag = parent->data; - - if (parent_tag && (parent_tag->flags & FL_BLOCK) && parent_tag->extra) { - bl_parent = parent_tag->extra; - - if (bl_parent->background_color.valid) { - memcpy (&bl->background_color, &bl_parent->background_color, - sizeof (bl->background_color)); - break; - } - } - } - } - if (!bl->font_color.valid) { - /* Try to propagate background color from parent nodes */ - for (parent = tag->parent; parent != NULL; parent = parent->parent) { - parent_tag = parent->data; - - if (parent_tag && (parent_tag->flags & FL_BLOCK) && parent_tag->extra) { - bl_parent = parent_tag->extra; - - if (bl_parent->font_color.valid) { - memcpy (&bl->font_color, &bl_parent->font_color, - sizeof (bl->font_color)); - break; + if (comp->len > 0) { + switch (comp->type) { + case RSPAMD_HTML_COMPONENT_COLOR: + fstr.begin = (gchar *) comp->start; + fstr.len = comp->len; + rspamd_html_process_color (comp->start, comp->len, + &bl->font_color); + msg_debug_html ("got color: %xd", bl->font_color.d.val); + break; + case RSPAMD_HTML_COMPONENT_BGCOLOR: + fstr.begin = (gchar *) comp->start; + fstr.len = comp->len; + rspamd_html_process_color (comp->start, comp->len, + &bl->background_color); + msg_debug_html ("got color: %xd", bl->font_color.d.val); + + if (tag->id == Tag_BODY) { + /* Set global background color */ + memcpy (&hc->bgcolor, &bl->background_color, + sizeof (hc->bgcolor)); } + break; + case RSPAMD_HTML_COMPONENT_STYLE: + bl->style.len = comp->len; + bl->style.start = comp->start; + msg_debug_html ("got style: %*s", (gint) bl->style.len, + bl->style.start); + rspamd_html_process_style (pool, bl, hc, comp->start, comp->len); + break; + case RSPAMD_HTML_COMPONENT_CLASS: + fstr.begin = (gchar *) comp->start; + fstr.len = comp->len; + bl->class = rspamd_mempool_ftokdup (pool, &fstr); + msg_debug_html ("got class: %s", bl->class); + break; + case RSPAMD_HTML_COMPONENT_SIZE: + fstr.begin = (gchar *) comp->start; + fstr.len = comp->len; + rspamd_html_process_color (comp->start, comp->len, + &bl->font_color); + msg_debug_html ("got color: %xd", bl->font_color.d.val); + break; + default: + /* NYI */ + break; } } - } - /* Set bgcolor to the html bgcolor and font color to black as a last resort */ - if (!bl->font_color.valid) { - bl->font_color.d.val = 0; - bl->font_color.d.comp.alpha = 255; - bl->font_color.valid = TRUE; - } - if (!bl->background_color.valid) { - memcpy (&bl->background_color, &hc->bgcolor, sizeof (hc->bgcolor)); + cur = g_list_next (cur); } if (hc->blocks == NULL) { @@ -2209,6 +2507,104 @@ rspamd_html_check_displayed_url (rspamd_mempool_t *pool, } } +static gboolean +rspamd_html_propagate_lengths (GNode *node, gpointer _unused) +{ + GNode *child; + struct html_tag *tag = node->data, *cld_tag; + + if (tag) { + child = node->children; + + /* Summarize content length from children */ + while (child) { + cld_tag = child->data; + tag->content_length += cld_tag->content_length; + child = child->next; + } + } + + return FALSE; +} + +static void +rspamd_html_propagate_style (struct html_content *hc, + struct html_tag *tag, + struct html_block *bl, + GQueue *blocks) +{ + struct html_block *bl_parent; + gboolean push_block = FALSE; + + + /* Propagate from the parent if needed */ + bl_parent = g_queue_peek_tail (blocks); + + if (bl_parent) { + if (!bl->background_color.valid) { + /* Try to propagate background color from parent nodes */ + if (bl_parent->background_color.valid) { + memcpy (&bl->background_color, &bl_parent->background_color, + sizeof (bl->background_color)); + } + } + else { + push_block = TRUE; + } + + if (!bl->font_color.valid) { + /* Try to propagate background color from parent nodes */ + if (bl_parent->font_color.valid) { + memcpy (&bl->font_color, &bl_parent->font_color, + sizeof (bl->font_color)); + } + } + else { + push_block = TRUE; + } + + /* Propagate font size */ + if (bl->font_size == (guint)-1) { + if (bl_parent->font_size != (guint)-1) { + bl->font_size = bl_parent->font_size; + } + } + else { + push_block = TRUE; + } + } + + /* Set bgcolor to the html bgcolor and font color to black as a last resort */ + if (!bl->font_color.valid) { + /* Don't touch opacity as it can be set separately */ + bl->font_color.d.comp.r = 0; + bl->font_color.d.comp.g = 0; + bl->font_color.d.comp.b = 0; + bl->font_color.valid = TRUE; + } + else { + push_block = TRUE; + } + + if (!bl->background_color.valid) { + memcpy (&bl->background_color, &hc->bgcolor, sizeof (hc->bgcolor)); + } + else { + push_block = TRUE; + } + + if (bl->font_size == (guint)-1) { + bl->font_size = 16; /* Default for browsers */ + } + else { + push_block = TRUE; + } + + if (push_block && !(tag->flags & FL_CLOSED)) { + g_queue_push_tail (blocks, bl); + } +} + GByteArray* rspamd_html_process_part_full (rspamd_mempool_t *pool, struct html_content *hc, GByteArray *in, GList **exceptions, GHashTable *urls, GHashTable *emails) @@ -2224,6 +2620,8 @@ rspamd_html_process_part_full (rspamd_mempool_t *pool, struct html_content *hc, gint substate = 0, len, href_offset = -1; struct html_tag *cur_tag = NULL, *content_tag = NULL; struct rspamd_url *url = NULL, *turl; + GQueue *styles_blocks; + enum { parse_start = 0, tag_begin, @@ -2256,6 +2654,7 @@ rspamd_html_process_part_full (rspamd_mempool_t *pool, struct html_content *hc, hc->bgcolor.valid = TRUE; dest = g_byte_array_sized_new (in->len / 3 * 2); + styles_blocks = g_queue_new (); p = in->data; c = p; @@ -2399,6 +2798,13 @@ rspamd_html_process_part_full (rspamd_mempool_t *pool, struct html_content *hc, p ++; } else { + if (content_tag) { + if (content_tag->content == NULL) { + content_tag->content = c; + } + + content_tag->content_length += p - c; + } state = tag_begin; } break; @@ -2570,10 +2976,10 @@ rspamd_html_process_part_full (rspamd_mempool_t *pool, struct html_content *hc, } save_space = FALSE; } - else if ((cur_tag->flags & (FL_CLOSED|FL_CLOSING)) && - (cur_tag->id == Tag_P || + + if ((cur_tag->id == Tag_P || cur_tag->id == Tag_TR || - cur_tag->id == Tag_DIV) && balanced) { + cur_tag->id == Tag_DIV)) { if (dest->len > 0 && dest->data[dest->len - 1] != '\n') { g_byte_array_append (dest, "\r\n", 2); } @@ -2582,7 +2988,7 @@ rspamd_html_process_part_full (rspamd_mempool_t *pool, struct html_content *hc, if (cur_tag->id == Tag_A || cur_tag->id == Tag_IFRAME) { if (!(cur_tag->flags & (FL_CLOSING))) { - url = rspamd_html_process_url_tag (pool, cur_tag); + url = rspamd_html_process_url_tag (pool, cur_tag, hc); if (url != NULL) { @@ -2651,21 +3057,62 @@ rspamd_html_process_part_full (rspamd_mempool_t *pool, struct html_content *hc, } } else if (cur_tag->id == Tag_LINK) { - url = rspamd_html_process_url_tag (pool, cur_tag); + url = rspamd_html_process_url_tag (pool, cur_tag, hc); + } + else if (cur_tag->id == Tag_BASE && !(cur_tag->flags & (FL_CLOSING))) { + struct html_tag *prev_tag = NULL; + + if (cur_level && cur_level->parent) { + prev_tag = cur_level->parent->data; + } + + /* + * Base is allowed only within head tag but we slightly + * relax that + */ + if (!prev_tag || prev_tag->id == Tag_HEAD || + prev_tag->id == Tag_HTML) { + url = rspamd_html_process_url_tag (pool, cur_tag, hc); + + if (url != NULL && hc->base_url == NULL) { + /* We have a base tag available */ + hc->base_url = url; + } + } } if (cur_tag->id == Tag_IMG && !(cur_tag->flags & FL_CLOSING)) { rspamd_html_process_img_tag (pool, cur_tag, hc); } - else if (!(cur_tag->flags & FL_CLOSING) && - (cur_tag->flags & FL_BLOCK)) { + else if (cur_tag->flags & FL_BLOCK) { struct html_block *bl; - rspamd_html_process_block_tag (pool, cur_tag, hc); - bl = cur_tag->extra; + if (cur_tag->flags & FL_CLOSING) { + /* Just remove block element from the queue if any */ + if (styles_blocks->length > 0) { + g_queue_pop_tail (styles_blocks); + } + } + else { + rspamd_html_process_block_tag (pool, cur_tag, hc); + bl = cur_tag->extra; + + if (bl) { + rspamd_html_propagate_style (hc, cur_tag, + cur_tag->extra, styles_blocks); + + /* Check visibility */ + if (bl->font_size < 3 || + bl->font_color.d.comp.alpha < 10) { - if (bl && !bl->visible) { - state = content_ignore; + bl->visible = FALSE; + msg_debug_html ("tag is not visible"); + } + + if (!bl->visible) { + state = content_ignore; + } + } } } } @@ -2681,6 +3128,13 @@ rspamd_html_process_part_full (rspamd_mempool_t *pool, struct html_content *hc, } } + if (hc->html_tags) { + g_node_traverse (hc->html_tags, G_POST_ORDER, G_TRAVERSE_ALL, -1, + rspamd_html_propagate_lengths, NULL); + } + + g_queue_free (styles_blocks); + return dest; } diff --git a/src/libserver/html.h b/src/libserver/html.h index 84d5e2bc2..c7534d309 100644 --- a/src/libserver/html.h +++ b/src/libserver/html.h @@ -33,7 +33,8 @@ enum html_component_type { RSPAMD_HTML_COMPONENT_STYLE, RSPAMD_HTML_COMPONENT_CLASS, RSPAMD_HTML_COMPONENT_WIDTH, - RSPAMD_HTML_COMPONENT_HEIGHT + RSPAMD_HTML_COMPONENT_HEIGHT, + RSPAMD_HTML_COMPONENT_SIZE, }; struct html_tag_component { @@ -94,9 +95,9 @@ struct html_block { struct html_tag { gint id; gint flags; - gsize content_length; - const gchar *content; + guint content_length; struct html_tag_component name; + const gchar *content; GQueue *params; gpointer extra; /** Additional data associated with tag (e.g. image) */ GNode *parent; @@ -106,6 +107,7 @@ struct html_tag { struct rspamd_task; struct html_content { + struct rspamd_url *base_url; GNode *html_tags; gint flags; guint total_tags; diff --git a/src/libserver/mempool_vars_internal.h b/src/libserver/mempool_vars_internal.h index 88cd273e8..a5195d325 100644 --- a/src/libserver/mempool_vars_internal.h +++ b/src/libserver/mempool_vars_internal.h @@ -25,6 +25,7 @@ #define RSPAMD_MEMPOOL_MTA_TAG "MTA-Tag" #define RSPAMD_MEMPOOL_MTA_NAME "MTA-Name" #define RSPAMD_MEMPOOL_SPF_DOMAIN "spf_domain" +#define RSPAMD_MEMPOOL_SPF_RECORD "spf_record" #define RSPAMD_MEMPOOL_PRINCIPAL_RECIPIENT "principal_recipient" #define RSPAMD_MEMPOOL_PROFILE "profile" #define RSPAMD_MEMPOOL_MILTER_REPLY "milter_reply" diff --git a/src/libserver/milter.c b/src/libserver/milter.c index c08513ea6..b44856028 100644 --- a/src/libserver/milter.c +++ b/src/libserver/milter.c @@ -1318,11 +1318,6 @@ rspamd_milter_macro_http (struct rspamd_milter_session *session, } } - IF_MACRO("{daemon_name}") { - rspamd_http_message_add_header_len (msg, MTA_TAG_HEADER, - found->begin, found->len); - } - IF_MACRO("{v}") { rspamd_http_message_add_header_len (msg, USER_AGENT_HEADER, found->begin, found->len); @@ -1807,6 +1802,9 @@ rspamd_milter_send_task_results (struct rspamd_milter_session *session, /* TODO: be more flexible about SMTP messages */ rspamd_milter_send_action (session, RSPAMD_MILTER_QUARANTINE, RSPAMD_MILTER_QUARANTINE_MESSAGE); + + /* Quarantine also requires accept action, all hail Sendmail */ + rspamd_milter_send_action (session, RSPAMD_MILTER_ACCEPT); } else { rcode = rspamd_fstring_new_init (RSPAMD_MILTER_RCODE_REJECT, diff --git a/src/libserver/monitored.c b/src/libserver/monitored.c index bd350e16d..7928615f9 100644 --- a/src/libserver/monitored.c +++ b/src/libserver/monitored.c @@ -14,11 +14,13 @@ * limitations under the License. */ +#include <contrib/librdns/rdns.h> #include "rdns.h" #include "mem_pool.h" #include "cfg_file.h" #include "cryptobox.h" #include "logger.h" +#include "contrib/uthash/utlist.h" static const gdouble default_monitoring_interval = 60.0; static const guint default_max_errors = 3; @@ -272,6 +274,8 @@ rspamd_monitored_dns_cb (struct rdns_reply *reply, void *arg) { struct rspamd_dns_monitored_conf *conf = arg; struct rspamd_monitored *m; + struct rdns_reply_entry *cur; + gboolean is_special_reply = FALSE; gdouble lat; m = conf->m; @@ -297,11 +301,30 @@ rspamd_monitored_dns_cb (struct rdns_reply *reply, void *arg) rspamd_monitored_propagate_success (m, lat); } else { - msg_info_mon ("DNS reply returned '%s' for %s while '%s' " - "was expected", - rdns_strerror (reply->code), - m->url, - rdns_strerror (conf->expected_code)); + LL_FOREACH (reply->entries, cur) { + if (cur->type == RDNS_REQUEST_A) { + if ((guint32)cur->content.a.addr.s_addr == + htonl (INADDR_LOOPBACK)) { + is_special_reply = TRUE; + } + } + } + + if (is_special_reply) { + msg_info_mon ("DNS query blocked on %s " + "(127.0.0.1 returned), " + "possibly due to high volume", + m->url); + } + else { + msg_info_mon ("DNS reply returned '%s' for %s while '%s' " + "was expected " + "(likely DNS spoofing or BL internal issues)", + rdns_strerror (reply->code), + m->url, + rdns_strerror (conf->expected_code)); + } + rspamd_monitored_propagate_error (m, "invalid return"); } } @@ -609,6 +632,7 @@ rspamd_monitored_ctx_destroy (struct rspamd_monitored_ctx *ctx) } g_ptr_array_free (ctx->elts, TRUE); + g_hash_table_unref (ctx->helts); g_free (ctx); } diff --git a/src/libserver/protocol.c b/src/libserver/protocol.c index 51b2fa0e5..c83451058 100644 --- a/src/libserver/protocol.c +++ b/src/libserver/protocol.c @@ -912,12 +912,10 @@ static ucl_object_t * rspamd_metric_result_ucl (struct rspamd_task *task, struct rspamd_metric_result *mres, ucl_object_t *top) { - GHashTableIter hiter; struct rspamd_symbol_result *sym; gboolean is_spam; enum rspamd_action_type action = METRIC_ACTION_NOACTION; ucl_object_t *obj = NULL, *sobj; - gpointer h, v; const gchar *subject; action = rspamd_check_action_metric (task, mres); @@ -966,13 +964,12 @@ rspamd_metric_result_ucl (struct rspamd_task *task, obj = ucl_object_typed_new (UCL_OBJECT); } - g_hash_table_iter_init (&hiter, mres->symbols); - - while (g_hash_table_iter_next (&hiter, &h, &v)) { - sym = (struct rspamd_symbol_result *)v; - sobj = rspamd_metric_symbol_ucl (task, sym); - ucl_object_insert_key (obj, sobj, h, 0, false); - } + kh_foreach_value_ptr (mres->symbols, sym, { + if (!(sym->flags & RSPAMD_SYMBOL_RESULT_IGNORED)) { + sobj = rspamd_metric_symbol_ucl (task, sym); + ucl_object_insert_key (obj, sobj, sym->name, 0, false); + } + }); if (task->cmd == CMD_CHECK_V2) { ucl_object_insert_key (top, obj, "symbols", 0, false); @@ -1403,8 +1400,6 @@ rspamd_protocol_write_log_pipe (struct rspamd_task *task) struct rspamd_protocol_log_message_sum *ls; lua_State *L = task->cfg->lua_state; struct rspamd_metric_result *mres; - GHashTableIter it; - gpointer k, v; struct rspamd_symbol_result *sym; gint id, i; guint32 *sid, n = 0, nextra = 0; @@ -1549,7 +1544,7 @@ rspamd_protocol_write_log_pipe (struct rspamd_task *task) mres = task->result; if (mres) { - n = g_hash_table_size (mres->symbols); + n = kh_size (mres->symbols); sz = sizeof (*ls) + sizeof (struct rspamd_protocol_log_symbol_result) * (n + nextra); @@ -1572,13 +1567,11 @@ rspamd_protocol_write_log_pipe (struct rspamd_task *task) ls->nresults = n; ls->nextra = nextra; - g_hash_table_iter_init (&it, mres->symbols); i = 0; - while (g_hash_table_iter_next (&it, &k, &v)) { + kh_foreach_value_ptr (mres->symbols, sym, { id = rspamd_symbols_cache_find_symbol (task->cfg->cache, - k); - sym = v; + sym->name); if (id >= 0) { ls->results[i].id = id; @@ -1590,7 +1583,7 @@ rspamd_protocol_write_log_pipe (struct rspamd_task *task) } i ++; - } + }); memcpy (&ls->results[n], extra->data, nextra * sizeof (er)); } diff --git a/src/libserver/roll_history.c b/src/libserver/roll_history.c index 4d1d34bd9..3df597816 100644 --- a/src/libserver/roll_history.c +++ b/src/libserver/roll_history.c @@ -77,6 +77,10 @@ roll_history_symbols_callback (gpointer key, gpointer value, void *user_data) struct rspamd_symbol_result *s = value; guint wr; + if (s->flags & RSPAMD_SYMBOL_RESULT_IGNORED) { + return; + } + if (cb->remain > 0) { wr = rspamd_snprintf (cb->pos, cb->remain, "%s, ", s->name); cb->pos += wr; @@ -155,9 +159,9 @@ rspamd_roll_history_update (struct roll_history *history, row->required_score = rspamd_task_get_required_score (task, metric_res); cbdata.pos = row->symbols; cbdata.remain = sizeof (row->symbols); - g_hash_table_foreach (metric_res->symbols, - roll_history_symbols_callback, - &cbdata); + rspamd_task_symbol_result_foreach (task, + roll_history_symbols_callback, + &cbdata); if (cbdata.remain > 0) { /* Remove last whitespace and comma */ *cbdata.pos-- = '\0'; @@ -236,7 +240,11 @@ rspamd_roll_history_load (struct roll_history *history, const gchar *filename) ucl_parser_free (parser); close (fd); - g_assert (top != NULL); + if (top == NULL) { + msg_warn ("cannot parse history file %s: no object", filename); + + return FALSE; + } if (ucl_object_type (top) != UCL_ARRAY) { msg_warn ("invalid object type read from: %s", filename); diff --git a/src/libserver/rspamd_control.c b/src/libserver/rspamd_control.c index bbfcdafdf..84c53700e 100644 --- a/src/libserver/rspamd_control.c +++ b/src/libserver/rspamd_control.c @@ -835,6 +835,7 @@ rspamd_srv_handler (gint fd, short what, gpointer ud) rdata->rep.id = cmd.id; rdata->rep.type = cmd.type; rdata->fd = -1; + worker->tmp_data = rdata; if (msg.msg_controllen >= CMSG_LEN (sizeof (int))) { rfd = *(int *) CMSG_DATA(CMSG_FIRSTHDR (&msg)); @@ -921,6 +922,7 @@ rspamd_srv_handler (gint fd, short what, gpointer ud) else if (what == EV_WRITE) { rdata = ud; worker = rdata->worker; + worker->tmp_data = NULL; /* Avoid race */ srv = rdata->srv; memset (&msg, 0, sizeof (msg)); @@ -967,6 +969,7 @@ rspamd_srv_start_watching (struct rspamd_main *srv, { g_assert (worker != NULL); + worker->tmp_data = NULL; event_set (&worker->srv_ev, worker->srv_pipe[0], EV_READ | EV_PERSIST, rspamd_srv_handler, worker); event_base_set (ev_base, &worker->srv_ev); diff --git a/src/libserver/spf.c b/src/libserver/spf.c index 8a19e3094..aa14bc750 100644 --- a/src/libserver/spf.c +++ b/src/libserver/spf.c @@ -576,6 +576,10 @@ spf_process_txt_record (struct spf_record *rec, struct spf_resolved_element *res if (strncmp (elt->content.txt.data, "v=spf1", sizeof ("v=spf1") - 1) == 0) { selected = elt; + rspamd_mempool_set_variable (rec->task->task_pool, + RSPAMD_MEMPOOL_SPF_RECORD, + rspamd_mempool_strdup (rec->task->task_pool, + elt->content.txt.data), NULL); break; } } @@ -584,6 +588,10 @@ spf_process_txt_record (struct spf_record *rec, struct spf_resolved_element *res LL_FOREACH (reply->entries, elt) { if (start_spf_parse (rec, resolved, elt->content.txt.data)) { ret = TRUE; + rspamd_mempool_set_variable (rec->task->task_pool, + RSPAMD_MEMPOOL_SPF_RECORD, + rspamd_mempool_strdup (rec->task->task_pool, + elt->content.txt.data), NULL); break; } } @@ -1579,7 +1587,7 @@ expand_spf_macro (struct spf_record *rec, struct spf_resolved_element *resolved, break; default: - assert (0); + g_assert_not_reached (); } } @@ -1649,35 +1657,71 @@ expand_spf_macro (struct spf_record *rec, struct spf_resolved_element *resolved, /* Read macro name */ switch (g_ascii_tolower (*p)) { case 'i': - macro_len = rspamd_strlcpy (ip_buf, - rspamd_inet_address_to_string (task->from_addr), - sizeof (ip_buf)); - macro_value = ip_buf; + if (task->from_addr) { + macro_len = rspamd_strlcpy (ip_buf, + rspamd_inet_address_to_string (task->from_addr), + sizeof (ip_buf)); + macro_value = ip_buf; + } + else { + macro_len = rspamd_snprintf (ip_buf, sizeof (ip_buf), + "127.0.0.1"); + macro_value = ip_buf; + } break; case 's': - macro_len = strlen (rec->sender); - macro_value = rec->sender; + if (rec->sender) { + macro_len = strlen (rec->sender); + macro_value = rec->sender; + } + else { + macro_len = sizeof ("unknown") - 1; + macro_value = "unknown"; + } break; case 'l': - macro_len = strlen (rec->local_part); - macro_value = rec->local_part; + if (rec->local_part) { + macro_len = strlen (rec->local_part); + macro_value = rec->local_part; + } + else { + macro_len = sizeof ("unknown") - 1; + macro_value = "unknown"; + } break; case 'o': - macro_len = strlen (rec->sender_domain); - macro_value = rec->sender_domain; + if (rec->sender_domain) { + macro_len = strlen (rec->sender_domain); + macro_value = rec->sender_domain; + } + else { + macro_len = sizeof ("unknown") - 1; + macro_value = "unknown"; + } break; case 'd': - macro_len = strlen (resolved->cur_domain); - macro_value = resolved->cur_domain; + if (resolved && resolved->cur_domain) { + macro_len = strlen (resolved->cur_domain); + macro_value = resolved->cur_domain; + } + else { + macro_len = sizeof ("unknown") - 1; + macro_value = "unknown"; + } break; case 'v': - if (rspamd_inet_address_get_af (task->from_addr) == AF_INET) { - macro_len = sizeof ("in-addr") - 1; - macro_value = "in-addr"; + if (task->from_addr) { + if (rspamd_inet_address_get_af (task->from_addr) == AF_INET) { + macro_len = sizeof ("in-addr") - 1; + macro_value = "in-addr"; + } else { + macro_len = sizeof ("ip6") - 1; + macro_value = "ip6"; + } } else { - macro_len = sizeof ("ip6") - 1; - macro_value = "ip6"; + macro_len = sizeof ("in-addr") - 1; + macro_value = "in-addr"; } break; case 'h': @@ -1692,10 +1736,12 @@ expand_spf_macro (struct spf_record *rec, struct spf_resolved_element *resolved, macro_value = task->helo; } } + else { + macro_len = sizeof ("unknown") - 1; + macro_value = "unknown"; + } break; default: - macro_len = 0; - macro_value = NULL; msg_info_spf ( "<%s>: spf error for domain %s: unknown or " "unsupported spf macro %c in %s", @@ -2023,6 +2069,7 @@ spf_dns_callback (struct rdns_reply *reply, gpointer arg) if (resolved) { if (!spf_process_txt_record (rec, resolved, reply)) { resolved = g_ptr_array_index(rec->resolved, 0); + if (rec->resolved->len > 1) { addr = g_ptr_array_index(resolved->elts, 0); if ((reply->code == RDNS_RC_NOREC || reply->code == RDNS_RC_NXDOMAIN) diff --git a/src/libserver/symbols_cache.c b/src/libserver/symbols_cache.c index 23ad15ed1..6076d1743 100644 --- a/src/libserver/symbols_cache.c +++ b/src/libserver/symbols_cache.c @@ -58,6 +58,7 @@ struct rspamd_symbols_cache_header { struct symbols_cache_order { GPtrArray *d; + guint id; ref_entry_t ref; }; @@ -78,24 +79,19 @@ struct symbols_cache { guint used_items; guint stats_symbols_count; guint64 total_hits; + guint id; struct rspamd_config *cfg; gdouble reload_time; gint peak_cb; }; -struct counter_data { - gdouble mean; - gdouble stddev; - guint64 number; -}; - struct item_stat { - struct counter_data time_counter; + struct rspamd_counter_data time_counter; gdouble avg_time; gdouble weight; guint hits; guint64 total_hits; - struct counter_data frequency_counter; + struct rspamd_counter_data frequency_counter; gdouble avg_frequency; gdouble stddev_frequency; }; @@ -107,7 +103,7 @@ struct cache_item { guint64 last_count; /* Per process counter */ - struct counter_data *cd; + struct rspamd_counter_data *cd; gchar *symbol; enum rspamd_symbol_type type; @@ -123,6 +119,8 @@ struct cache_item { gint parent; /* Priority */ gint priority; + /* Topological order */ + guint order; gint id; gint frequency_peaks; @@ -229,12 +227,14 @@ rspamd_symbols_cache_order_unref (gpointer p) } static struct symbols_cache_order * -rspamd_symbols_cache_order_new (gsize nelts) +rspamd_symbols_cache_order_new (struct symbols_cache *cache, + gsize nelts) { struct symbols_cache_order *ord; ord = g_malloc0 (sizeof (*ord)); ord->d = g_ptr_array_sized_new (nelts); + ord->id = cache->id; REF_INIT_RETAIN (ord, rspamd_symbols_cache_order_dtor); return ord; @@ -280,6 +280,12 @@ prefilters_cmp (const void *p1, const void *p2, gpointer ud) return 0; } +#define TSORT_MARK_PERM(it) (it)->order |= (1u << 31) +#define TSORT_MARK_TEMP(it) (it)->order |= (1u << 30) +#define TSORT_IS_MARKED_PERM(it) ((it)->order & (1u << 31)) +#define TSORT_IS_MARKED_TEMP(it) ((it)->order & (1u << 30)) +#define TSORT_UNMASK(it) ((it)->order & ~((1u << 31) | (1u << 30))) + static gint cache_logic_cmp (const void *p1, const void *p2, gpointer ud) { @@ -289,35 +295,31 @@ cache_logic_cmp (const void *p1, const void *p2, gpointer ud) double w1, w2; double weight1, weight2; double f1 = 0, f2 = 0, t1, t2, avg_freq, avg_weight; - - if (i1->deps->len != 0 || i2->deps->len != 0) { - /* TODO: handle complex dependencies */ - w1 = 1.0; - w2 = 1.0; - - if (i1->deps->len != 0) { - w1 = 1.0 / (i1->deps->len); - } - if (i2->deps->len != 0) { - w2 = 1.0 / (i2->deps->len); + guint o1 = TSORT_UNMASK (i1), o2 = TSORT_UNMASK (i2); + + + if (o1 == o2) { + /* Heurstic */ + if (i1->priority == i2->priority) { + avg_freq = ((gdouble) cache->total_hits / cache->used_items); + avg_weight = (cache->total_weight / cache->used_items); + f1 = (double) i1->st->total_hits / avg_freq; + f2 = (double) i2->st->total_hits / avg_freq; + weight1 = fabs (i1->st->weight) / avg_weight; + weight2 = fabs (i2->st->weight) / avg_weight; + t1 = i1->st->avg_time; + t2 = i2->st->avg_time; + w1 = SCORE_FUN (weight1, f1, t1); + w2 = SCORE_FUN (weight2, f2, t2); + } else { + /* Strict sorting */ + w1 = abs (i1->priority); + w2 = abs (i2->priority); } } - else if (i1->priority == i2->priority) { - avg_freq = ((gdouble)cache->total_hits / cache->used_items); - avg_weight = (cache->total_weight / cache->used_items); - f1 = (double)i1->st->total_hits / avg_freq; - f2 = (double)i2->st->total_hits / avg_freq; - weight1 = fabs (i1->st->weight) / avg_weight; - weight2 = fabs (i2->st->weight) / avg_weight; - t1 = i1->st->avg_time; - t2 = i2->st->avg_time; - w1 = SCORE_FUN (weight1, f1, t1); - w2 = SCORE_FUN (weight2, f2, t2); - } else { - /* Strict sorting */ - w1 = abs (i1->priority); - w2 = abs (i2->priority); + w1 = o1; + w2 = o2; } if (w2 > w1) { @@ -330,51 +332,43 @@ cache_logic_cmp (const void *p1, const void *p2, gpointer ud) return 0; } -/** - * Set counter for a symbol using moving average - */ -static double -rspamd_set_counter (struct counter_data *cd, gdouble value) +static void +rspamd_symbols_cache_tsort_visit (struct symbols_cache *cache, + struct cache_item *it, + guint cur_order) { - gdouble cerr; + struct cache_dependency *dep; + guint i; - /* Cumulative moving average using per-process counter data */ - if (cd->number == 0) { - cd->mean = 0; - cd->stddev = 0; + if (TSORT_IS_MARKED_PERM (it)) { + if (cur_order > TSORT_UNMASK (it)) { + /* Need to recalculate the whole chain */ + it->order = cur_order; /* That also removes all masking */ + } + else { + /* We are fine, stop DFS */ + return; + } + } + else if (TSORT_IS_MARKED_TEMP (it)) { + msg_err_cache ("cyclic dependencies found when checking '%s'!", + it->symbol); + return; } - cd->mean += (value - cd->mean) / (gdouble)(++cd->number); - cerr = (value - cd->mean) * (value - cd->mean); - cd->stddev += (cerr - cd->stddev) / (gdouble)(cd->number); - - return cd->mean; -} - -/** - * Set counter for a symbol using exponential moving average - */ -static double -rspamd_set_counter_ema (struct counter_data *cd, gdouble value, gdouble alpha) -{ - gdouble diff, incr; + TSORT_MARK_TEMP (it); + msg_debug_cache ("visiting node: %s (%d)", it->symbol, cur_order); - /* Cumulative moving average using per-process counter data */ - if (cd->number == 0) { - cd->mean = 0; - cd->stddev = 0; + PTR_ARRAY_FOREACH (it->deps, i, dep) { + msg_debug_cache ("visiting dep: %s (%d)", dep->item->symbol, cur_order + 1); + rspamd_symbols_cache_tsort_visit (cache, dep->item, cur_order + 1); } - diff = value - cd->mean; - incr = diff * alpha; - cd->mean += incr; - cd->stddev = (1 - alpha) * (cd->stddev + diff * incr); - cd->number ++; + it->order = cur_order; - return cd->mean; + TSORT_MARK_PERM (it); } - static void rspamd_symbols_cache_resort (struct symbols_cache *cache) { @@ -383,7 +377,7 @@ rspamd_symbols_cache_resort (struct symbols_cache *cache) guint64 total_hits = 0; struct cache_item *it; - ord = rspamd_symbols_cache_order_new (cache->used_items); + ord = rspamd_symbols_cache_order_new (cache, cache->used_items); for (i = 0; i < cache->used_items; i ++) { it = g_ptr_array_index (cache->items_by_id, i); @@ -392,12 +386,32 @@ rspamd_symbols_cache_resort (struct symbols_cache *cache) if (!(it->type & (SYMBOL_TYPE_PREFILTER| SYMBOL_TYPE_POSTFILTER| SYMBOL_TYPE_COMPOSITE))) { - g_ptr_array_add (ord->d, it); + if (it->parent == -1 && it->func) { + it->order = 0; + g_ptr_array_add (ord->d, it); + } } } - cache->total_hits = total_hits; + /* Topological sort, intended to be O(N) but my implementation + * is not linear (semi-linear usually) as I want to make it as + * simple as possible. + * On each stage it does DFS for unseen nodes. In theory, that + * can be more complicated than linear - O(N^2) for specially + * crafted data. But I don't care. + */ + PTR_ARRAY_FOREACH (ord->d, i, it) { + if (it->order == 0) { + rspamd_symbols_cache_tsort_visit (cache, it, 1); + } + } + + /* + * Now we have all sorted and can do some heuristical sort, keeping + * topological order invariant + */ g_ptr_array_sort_with_data (ord->d, cache_logic_cmp, cache); + cache->total_hits = total_hits; if (cache->items_by_order) { REF_RELEASE (cache->items_by_order); @@ -418,8 +432,6 @@ rspamd_symbols_cache_post_init (struct symbols_cache *cache) gint i, j; gint id; - rspamd_symbols_cache_resort (cache); - cur = cache->delayed_deps; while (cur) { ddep = cur->data; @@ -521,6 +533,8 @@ rspamd_symbols_cache_post_init (struct symbols_cache *cache) g_ptr_array_sort_with_data (cache->prefilters, prefilters_cmp, cache); g_ptr_array_sort_with_data (cache->postfilters, postfilters_cmp, cache); g_ptr_array_sort_with_data (cache->idempotent, postfilters_cmp, cache); + + rspamd_symbols_cache_resort (cache); } static gboolean @@ -810,7 +824,7 @@ rspamd_symbols_cache_add_symbol (struct symbols_cache *cache, * save or accumulate */ item->cd = rspamd_mempool_alloc0 (cache->static_pool, - sizeof (struct counter_data)); + sizeof (struct rspamd_counter_data)); item->func = func; item->user_data = user_data; item->priority = priority; @@ -824,6 +838,7 @@ rspamd_symbols_cache_add_symbol (struct symbols_cache *cache, item->id = cache->used_items; item->parent = parent; cache->used_items ++; + cache->id ++; if (!(item->type & (SYMBOL_TYPE_IDEMPOTENT|SYMBOL_TYPE_NOSTAT|SYMBOL_TYPE_CLASSIFIER))) { @@ -896,6 +911,7 @@ rspamd_symbols_cache_add_condition (struct symbols_cache *cache, gint id, } item->condition_cb = cbref; + cache->id ++; msg_debug_cache ("adding condition at lua ref %d to %s (%d)", cbref, item->symbol, item->id); @@ -939,6 +955,7 @@ rspamd_symbols_cache_add_condition_delayed (struct symbols_cache *cache, ncond->sym = g_strdup (sym); ncond->cbref = cbref; ncond->L = L; + cache->id ++; cache->delayed_conditions = g_list_prepend (cache->delayed_conditions, ncond); @@ -1036,6 +1053,7 @@ rspamd_symbols_cache_new (struct rspamd_config *cfg) cache->cfg = cfg; cache->cksum = 0xdeadbabe; cache->peak_cb = -1; + cache->id = rspamd_random_uint64_fast (); return cache; } @@ -1276,9 +1294,11 @@ rspamd_symbols_cache_watcher_cb (gpointer sessiond, gpointer ud) remain ++; } else { - msg_debug_task ("watcher for %d, unblocked item %d", + msg_debug_task ("watcher for %d(%s), unblocked item %d(%s)", item->id, - it->id); + item->symbol, + it->id, + it->symbol); rspamd_symbols_cache_check_symbol (task, cache, it, checkpoint, NULL); @@ -1287,7 +1307,8 @@ rspamd_symbols_cache_watcher_cb (gpointer sessiond, gpointer ud) } } - msg_debug_task ("finished watcher for %d, %ud symbols waiting", item->id, + msg_debug_task ("finished watcher for %d(%s), %ud symbols waiting", + item->id, item->symbol, remain); } @@ -1419,8 +1440,8 @@ rspamd_symbols_cache_check_deps (struct rspamd_task *task, if (dep->item == NULL) { /* Assume invalid deps as done */ - msg_debug_task ("symbol %s has invalid dependencies from %s", - item->symbol, dep->sym); + msg_debug_task ("symbol %d(%s) has invalid dependencies on %d(%s)", + item->id, item->symbol, dep->id, dep->sym); continue; } @@ -1449,8 +1470,9 @@ rspamd_symbols_cache_check_deps (struct rspamd_task *task, } ret = FALSE; - msg_debug_task ("delayed dependency %d for symbol %d", - dep->id, item->id); + msg_debug_task ("delayed dependency %d(%s) for " + "symbol %d(%s)", + dep->id, dep->sym, item->id, item->symbol); } else if (!rspamd_symbols_cache_check_symbol (task, cache, dep->item, @@ -1458,29 +1480,39 @@ rspamd_symbols_cache_check_deps (struct rspamd_task *task, NULL)) { /* Now started, but has events pending */ ret = FALSE; - msg_debug_task ("started check of %d symbol as dep for " - "%d", - dep->id, item->id); + msg_debug_task ("started check of %d(%s) symbol " + "as dep for " + "%d(%s)", + dep->id, dep->sym, item->id, item->symbol); } else { - msg_debug_task ("dependency %d for symbol %d is " + msg_debug_task ("dependency %d(%s) for symbol %d(%s) is " "already processed", - dep->id, item->id); + dep->id, dep->sym, item->id, item->symbol); } } else { + msg_debug_task ("dependency %d(%s) for symbol %d(%s) " + "cannot be started now", + dep->id, dep->sym, + item->id, item->symbol); ret = FALSE; } } else { /* Started but not finished */ + msg_debug_task ("dependency %d(%s) for symbol %d(%s) is " + "still executing", + dep->id, dep->sym, + item->id, item->symbol); ret = FALSE; } } else { - msg_debug_task ("dependency %d for symbol %d is already " + msg_debug_task ("dependency %d(%s) for symbol %d(%s) is already " "checked", - dep->id, item->id); + dep->id, dep->sym, + item->id, item->symbol); } } } @@ -1510,19 +1542,14 @@ rspamd_symbols_cache_make_checkpoint (struct rspamd_task *task, struct symbols_cache *cache) { struct cache_savepoint *checkpoint; - guint nitems; - nitems = cache->items_by_id->len - cache->postfilters->len - - cache->prefilters->len - cache->composites->len - - cache->idempotent->len; - - if (nitems != cache->items_by_order->d->len) { + if (cache->items_by_order->id != cache->id) { /* * Cache has been modified, need to resort it */ msg_info_cache ("symbols cache has been modified since last check:" - " old items: %ud, new items: %ud", - cache->items_by_order->d->len, nitems); + " old id: %ud, new id: %ud", + cache->items_by_order->id, cache->id); rspamd_symbols_cache_resort (cache); } @@ -1759,9 +1786,9 @@ rspamd_symbols_cache_process_symbols (struct rspamd_task * task, guint j; struct cache_item *tmp_it; - msg_debug_task ("blocked execution of %d unless deps are " + msg_debug_task ("blocked execution of %d(%s) unless deps are " "resolved", - item->id); + item->id, item->symbol); PTR_ARRAY_FOREACH (checkpoint->waitq, j, tmp_it) { if (item->id == tmp_it->id) { @@ -2168,7 +2195,7 @@ rspamd_symbols_cache_resort_cb (gint fd, short what, gpointer ud) } cbdata->last_resort = cur_ticks; - rspamd_symbols_cache_resort (cache); + /* We don't do actual sorting due to topological guarantees */ } void diff --git a/src/libserver/symbols_cache.h b/src/libserver/symbols_cache.h index 4e1c10dc5..2657b07cc 100644 --- a/src/libserver/symbols_cache.h +++ b/src/libserver/symbols_cache.h @@ -43,6 +43,7 @@ enum rspamd_symbol_type { SYMBOL_TYPE_NOSTAT = (1 << 11), /* Skip as statistical symbol */ SYMBOL_TYPE_IDEMPOTENT = (1 << 12), /* Symbol cannot change metric */ SYMBOL_TYPE_SQUEEZED = (1 << 13), /* Symbol is squeezed inside Lua */ + SYMBOL_TYPE_TRIVIAL = (1 << 14), /* Symbol is trivial */ }; /** diff --git a/src/libserver/task.c b/src/libserver/task.c index 9be780b1b..437bc4829 100644 --- a/src/libserver/task.c +++ b/src/libserver/task.c @@ -69,6 +69,7 @@ rspamd_task_new (struct rspamd_worker *worker, struct rspamd_config *cfg, new_task = g_malloc0 (sizeof (struct rspamd_task)); new_task->worker = worker; + new_task->lang_det = lang_det; if (cfg) { new_task->cfg = cfg; @@ -78,7 +79,14 @@ rspamd_task_new (struct rspamd_worker *worker, struct rspamd_config *cfg, new_task->flags |= RSPAMD_TASK_FLAG_PASS_ALL; } - new_task->re_rt = rspamd_re_cache_runtime_new (cfg->re_cache); + + if (cfg->re_cache) { + new_task->re_rt = rspamd_re_cache_runtime_new (cfg->re_cache); + } + + if (new_task->lang_det == NULL && cfg->lang_det != NULL) { + new_task->lang_det = cfg->lang_det; + } } gettimeofday (&new_task->tv, NULL); @@ -86,7 +94,6 @@ rspamd_task_new (struct rspamd_worker *worker, struct rspamd_config *cfg, new_task->time_virtual = rspamd_get_virtual_ticks (); new_task->time_real_finish = NAN; new_task->time_virtual_finish = NAN; - new_task->lang_det = lang_det; if (pool == NULL) { new_task->task_pool = @@ -263,7 +270,10 @@ rspamd_task_free (struct rspamd_task *task) } ucl_object_unref (task->messages); - rspamd_re_cache_runtime_destroy (task->re_rt); + + if (task->re_rt) { + rspamd_re_cache_runtime_destroy (task->re_rt); + } if (task->http_conn != NULL) { rspamd_http_connection_reset (task->http_conn); @@ -1035,9 +1045,7 @@ rspamd_task_log_metric_res (struct rspamd_task *task, static gchar scorebuf[32]; rspamd_ftok_t res = {.begin = NULL, .len = 0}; struct rspamd_metric_result *mres; - GHashTableIter it; gboolean first = TRUE; - gpointer k, v; rspamd_fstring_t *symbuf; struct rspamd_symbol_result *sym; GPtrArray *sorted_symbols; @@ -1073,12 +1081,13 @@ rspamd_task_log_metric_res (struct rspamd_task *task, break; case RSPAMD_LOG_SYMBOLS: symbuf = rspamd_fstring_sized_new (128); - g_hash_table_iter_init (&it, mres->symbols); - sorted_symbols = g_ptr_array_sized_new (g_hash_table_size (mres->symbols)); + sorted_symbols = g_ptr_array_sized_new (kh_size (mres->symbols)); - while (g_hash_table_iter_next (&it, &k, &v)) { - g_ptr_array_add (sorted_symbols, v); - } + kh_foreach_value_ptr (mres->symbols, sym, { + if (!(sym->flags & RSPAMD_SYMBOL_RESULT_IGNORED)) { + g_ptr_array_add (sorted_symbols, (gpointer)sym); + } + }); g_ptr_array_sort (sorted_symbols, rspamd_task_compare_log_sym); diff --git a/src/libserver/url.c b/src/libserver/url.c index 650f86a28..653cc3570 100644 --- a/src/libserver/url.c +++ b/src/libserver/url.c @@ -475,42 +475,56 @@ rspamd_url_add_static_matchers (struct url_match_scanner *sc) } void +rspamd_url_deinit (void) +{ + if (url_scanner != NULL) { + rspamd_multipattern_destroy (url_scanner->search_trie); + g_array_free (url_scanner->matchers, TRUE); + g_free (url_scanner); + + url_scanner = NULL; + } +} + +void rspamd_url_init (const gchar *tld_file) { GError *err = NULL; - if (url_scanner == NULL) { - url_scanner = g_malloc (sizeof (struct url_match_scanner)); + if (url_scanner != NULL) { + rspamd_url_deinit (); + } - if (tld_file) { - /* Reserve larger multipattern */ - url_scanner->matchers = g_array_sized_new (FALSE, TRUE, - sizeof (struct url_matcher), 13000); - url_scanner->search_trie = rspamd_multipattern_create_sized (13000, - RSPAMD_MULTIPATTERN_TLD | RSPAMD_MULTIPATTERN_ICASE); - } - else { - url_scanner->matchers = g_array_sized_new (FALSE, TRUE, - sizeof (struct url_matcher), 128); - url_scanner->search_trie = rspamd_multipattern_create_sized (128, - RSPAMD_MULTIPATTERN_TLD | RSPAMD_MULTIPATTERN_ICASE); - } + url_scanner = g_malloc (sizeof (struct url_match_scanner)); - rspamd_url_add_static_matchers (url_scanner); + if (tld_file) { + /* Reserve larger multipattern */ + url_scanner->matchers = g_array_sized_new (FALSE, TRUE, + sizeof (struct url_matcher), 13000); + url_scanner->search_trie = rspamd_multipattern_create_sized (13000, + RSPAMD_MULTIPATTERN_TLD | RSPAMD_MULTIPATTERN_ICASE); + } + else { + url_scanner->matchers = g_array_sized_new (FALSE, TRUE, + sizeof (struct url_matcher), 128); + url_scanner->search_trie = rspamd_multipattern_create_sized (128, + RSPAMD_MULTIPATTERN_TLD | RSPAMD_MULTIPATTERN_ICASE); + } - if (tld_file != NULL) { - rspamd_url_parse_tld_file (tld_file, url_scanner); - } + rspamd_url_add_static_matchers (url_scanner); - if (!rspamd_multipattern_compile (url_scanner->search_trie, &err)) { - msg_err ("cannot compile tld patterns, url matching will be " - "broken completely: %e", err); - g_error_free (err); - } + if (tld_file != NULL) { + rspamd_url_parse_tld_file (tld_file, url_scanner); + } - msg_debug ("initialized trie of %ud elements", - url_scanner->matchers->len); + if (!rspamd_multipattern_compile (url_scanner->search_trie, &err)) { + msg_err ("cannot compile tld patterns, url matching will be " + "broken completely: %e", err); + g_error_free (err); } + + msg_debug ("initialized trie of %ud elements", + url_scanner->matchers->len); } #define SET_U(u, field) do { \ @@ -1638,7 +1652,7 @@ rspamd_url_parse (struct rspamd_url *uri, gchar *uristring, gsize len, memcpy (uri->string + u.field_data[UF_SCHEMA].len, "://", 3); rspamd_strlcpy (uri->string + u.field_data[UF_SCHEMA].len + 3, p + u.field_data[UF_SCHEMA].len + 1, - len - 1 - u.field_data[UF_SCHEMA].len); + len - 2 - u.field_data[UF_SCHEMA].len); /* Compensate slashes added */ for (i = UF_SCHEMA + 1; i < UF_MAX; i++) { if (u.field_set & (1 << i)) { diff --git a/src/libserver/url.h b/src/libserver/url.h index 0eac98060..b0cc10239 100644 --- a/src/libserver/url.h +++ b/src/libserver/url.h @@ -91,7 +91,7 @@ enum rspamd_url_protocol { * @param cfg */ void rspamd_url_init (const gchar *tld_file); - +void rspamd_url_deinit (void); /* * Parse urls inside text * @param pool memory pool diff --git a/src/libserver/worker_util.c b/src/libserver/worker_util.c index 8c4e934bc..cf51eff62 100644 --- a/src/libserver/worker_util.c +++ b/src/libserver/worker_util.c @@ -44,6 +44,16 @@ #endif #include "zlib.h" +#ifdef WITH_LIBUNWIND +#define UNW_LOCAL_ONLY 1 +#include <libunwind.h> +#define UNWIND_BACKTRACE_DEPTH 256 +#endif + +#ifdef HAVE_UCONTEXT_H +#include <ucontext.h> +#endif + static void rspamd_worker_ignore_signal (int signo); /** * Return worker's control structure by its type @@ -300,7 +310,6 @@ rspamd_prepare_worker (struct rspamd_worker *worker, const char *name, gperf_profiler_init (worker->srv->cfg, name); - worker->srv->pid = getpid (); worker->signal_events = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, rspamd_sigh_free); @@ -342,7 +351,6 @@ rspamd_worker_stop_accept (struct rspamd_worker *worker) { GList *cur; struct event *events; - struct rspamd_map *map; /* Remove all events */ cur = worker->accept_events; @@ -381,17 +389,6 @@ rspamd_worker_stop_accept (struct rspamd_worker *worker) g_hash_table_unref (worker->signal_events); #endif - - /* Cleanup maps */ - for (cur = worker->srv->cfg->maps; cur != NULL; cur = g_list_next (cur)) { - map = cur->data; - - if (map->dtor) { - map->dtor (map->dtor_data); - } - - map->dtor = NULL; - } } static rspamd_fstring_t * @@ -452,7 +449,14 @@ rspamd_controller_send_string (struct rspamd_http_connection_entry *entry, msg->date = time (NULL); msg->code = 200; msg->status = rspamd_fstring_new_init ("OK", 2); - reply = rspamd_fstring_new_init (str, strlen (str)); + + if (str) { + reply = rspamd_fstring_new_init (str, strlen (str)); + } + else { + reply = rspamd_fstring_new_init ("null", 4); + } + rspamd_http_message_set_body_from_fstring_steal (msg, rspamd_controller_maybe_compress (entry, reply, msg)); rspamd_http_connection_reset (entry->conn); @@ -933,4 +937,131 @@ rspamd_worker_init_monitored (struct rspamd_worker *worker, rspamd_monitored_ctx_config (worker->srv->cfg->monitored_ctx, worker->srv->cfg, ev_base, resolver->r, rspamd_worker_monitored_on_change, worker); +} + +#ifdef HAVE_SA_SIGINFO + +#ifdef WITH_LIBUNWIND +static void +rspamd_print_crash (ucontext_t *uap) +{ + unw_cursor_t cursor; + unw_word_t ip, off; + guint level; + gint ret; + + if ((ret = unw_init_local (&cursor, uap)) != 0) { + msg_err ("unw_init_local: %d", ret); + + return; + } + + level = 0; + ret = 0; + + for (;;) { + char name[128]; + + if (level >= UNWIND_BACKTRACE_DEPTH) { + break; + } + + unw_get_reg (&cursor, UNW_REG_IP, &ip); + ret = unw_get_proc_name(&cursor, name, sizeof (name), &off); + + if (ret == 0) { + msg_err ("%d: %p: %s()+0x%xl", + level, ip, name, (uintptr_t)off); + } else { + msg_err ("%d: %p: <unknown>", level, ip); + } + + level++; + ret = unw_step (&cursor); + + if (ret <= 0) { + break; + } + } + + if (ret < 0) { + msg_err ("unw_step_ptr: %d", ret); + } +} +#endif + +static struct rspamd_main *saved_main = NULL; +static gboolean +rspamd_crash_propagate (gpointer key, gpointer value, gpointer unused) +{ + struct rspamd_worker *w = value; + + /* Kill children softly */ + kill (w->pid, SIGTERM); + + return TRUE; +} + +static void +rspamd_crash_sig_handler (int sig, siginfo_t *info, void *ctx) +{ + struct sigaction sa; + ucontext_t *uap = ctx; + pid_t pid; + + pid = getpid (); + msg_err ("caught fatal signal %d(%s), " + "pid: %P, trace: ", + sig, strsignal (sig), pid); + (void)uap; +#ifdef WITH_LIBUNWIND + rspamd_print_crash (uap); +#endif + + if (saved_main) { + if (pid == saved_main->pid) { + /* + * Main process has crashed, propagate crash further to trigger + * monitoring alerts and mass panic + */ + g_hash_table_foreach_remove (saved_main->workers, + rspamd_crash_propagate, NULL); + } + } + + /* + * Invoke signal with the default handler + */ + sigemptyset (&sa.sa_mask); + sa.sa_handler = SIG_DFL; + sa.sa_flags = 0; + sigaction (sig, &sa, NULL); + kill (pid, sig); +} +#endif + +void +rspamd_set_crash_handler (struct rspamd_main *rspamd_main) +{ +#ifdef HAVE_SA_SIGINFO + struct sigaction sa; + +#ifdef HAVE_SIGALTSTACK + stack_t ss; + + /* Allocate special stack, NOT freed at the end so far */ + ss.ss_size = MAX (SIGSTKSZ, 8192 * 4); + ss.ss_sp = g_malloc0 (ss.ss_size); + sigaltstack (&ss, NULL); +#endif + saved_main = rspamd_main; + sigemptyset (&sa.sa_mask); + sa.sa_sigaction = &rspamd_crash_sig_handler; + sa.sa_flags = SA_RESTART | SA_SIGINFO | SA_ONSTACK; + sigaction (SIGSEGV, &sa, NULL); + sigaction (SIGBUS, &sa, NULL); + sigaction (SIGABRT, &sa, NULL); + sigaction (SIGFPE, &sa, NULL); + sigaction (SIGSYS, &sa, NULL); +#endif }
\ No newline at end of file diff --git a/src/libserver/worker_util.h b/src/libserver/worker_util.h index 2e3fd4458..dbcc8f8a2 100644 --- a/src/libserver/worker_util.h +++ b/src/libserver/worker_util.h @@ -188,6 +188,11 @@ struct rspamd_worker *rspamd_fork_worker (struct rspamd_main *, struct rspamd_worker_conf *, guint idx, struct event_base *ev_base); /** + * Sets crash signals handlers if compiled with libunwind + */ +void rspamd_set_crash_handler (struct rspamd_main *); + +/** * Initialise the main monitoring worker * @param worker * @param ev_base diff --git a/src/libstat/CMakeLists.txt b/src/libstat/CMakeLists.txt index 0bc920616..1019a9925 100644 --- a/src/libstat/CMakeLists.txt +++ b/src/libstat/CMakeLists.txt @@ -12,12 +12,10 @@ SET(BACKENDSSRC ${CMAKE_CURRENT_SOURCE_DIR}/backends/mmaped_file.c ${CMAKE_CURRENT_SOURCE_DIR}/backends/sqlite3_backend.c) SET(CACHESSRC ${CMAKE_CURRENT_SOURCE_DIR}/learn_cache/sqlite3_cache.c) -IF(ENABLE_HIREDIS MATCHES "ON") - SET(BACKENDSSRC ${BACKENDSSRC} - ${CMAKE_CURRENT_SOURCE_DIR}/backends/redis_backend.c) - SET(CACHESSRC ${CACHESSRC} - ${CMAKE_CURRENT_SOURCE_DIR}/learn_cache/redis_cache.c) -ENDIF(ENABLE_HIREDIS MATCHES "ON") +SET(BACKENDSSRC ${BACKENDSSRC} + ${CMAKE_CURRENT_SOURCE_DIR}/backends/redis_backend.c) +SET(CACHESSRC ${CACHESSRC} + ${CMAKE_CURRENT_SOURCE_DIR}/learn_cache/redis_cache.c) SET(RSPAMD_STAT ${LIBSTATSRC} diff --git a/src/libstat/backends/redis_backend.c b/src/libstat/backends/redis_backend.c index 79fafd15b..69c14e167 100644 --- a/src/libstat/backends/redis_backend.c +++ b/src/libstat/backends/redis_backend.c @@ -25,6 +25,12 @@ #include "adapters/libevent.h" #include "ref.h" +#define msg_debug_stat_redis(...) rspamd_conditional_debug_fast (NULL, NULL, \ + rspamd_stat_redis_log_id, "stat_redis", task->task_pool->tag.uid, \ + G_STRFUNC, \ + __VA_ARGS__) + +INIT_LOG_MODULE(stat_redis) #define REDIS_CTX(p) (struct redis_stat_ctx *)(p) #define REDIS_RUNTIME(p) (struct redis_stat_runtime *)(p) @@ -909,7 +915,8 @@ rspamd_redis_stat_keys (redisAsyncContext *c, gpointer r, gpointer priv) else { msg_err ("cannot get keys to gather stat: unknown error"); } - rspamd_upstream_fail (cbdata->selected); + + rspamd_upstream_fail (cbdata->selected, FALSE); rspamd_redis_async_cbdata_cleanup (cbdata); } } @@ -1031,7 +1038,7 @@ rspamd_redis_timeout (gint fd, short what, gpointer d) msg_err_task_check ("connection to redis server %s timed out", rspamd_upstream_name (rt->selected)); - rspamd_upstream_fail (rt->selected); + rspamd_upstream_fail (rt->selected, FALSE); if (rt->redis) { redis = rt->redis; @@ -1083,7 +1090,7 @@ rspamd_redis_connected (redisAsyncContext *c, gpointer r, gpointer priv) } rt->learned = val; - msg_debug_task ("connected to redis server, tokens learned for %s: %uL", + msg_debug_stat_redis ("connected to redis server, tokens learned for %s: %uL", rt->redis_object_expanded, rt->learned); rspamd_upstream_ok (rt->selected); } @@ -1091,7 +1098,7 @@ rspamd_redis_connected (redisAsyncContext *c, gpointer r, gpointer priv) else { msg_err_task ("error getting reply from redis server %s: %s", rspamd_upstream_name (rt->selected), c->errstr); - rspamd_upstream_fail (rt->selected); + rspamd_upstream_fail (rt->selected, FALSE); if (!rt->err) { g_set_error (&rt->err, rspamd_redis_stat_quark (), c->err, @@ -1168,7 +1175,7 @@ rspamd_redis_processed (redisAsyncContext *c, gpointer r, gpointer priv) rspamd_redis_type_to_string (reply->type)); } - msg_debug_task_check ("received tokens for %s: %d processed, %d found", + msg_debug_stat_redis ("received tokens for %s: %d processed, %d found", rt->redis_object_expanded, processed, found); rspamd_upstream_ok (rt->selected); } @@ -1178,7 +1185,7 @@ rspamd_redis_processed (redisAsyncContext *c, gpointer r, gpointer priv) rspamd_upstream_name (rt->selected), c->errstr); if (rt->redis) { - rspamd_upstream_fail (rt->selected); + rspamd_upstream_fail (rt->selected, FALSE); } if (!rt->err) { @@ -1210,7 +1217,7 @@ rspamd_redis_learned (redisAsyncContext *c, gpointer r, gpointer priv) rspamd_upstream_name (rt->selected), c->errstr); if (rt->redis) { - rspamd_upstream_fail (rt->selected); + rspamd_upstream_fail (rt->selected, FALSE); } if (!rt->err) { @@ -1700,6 +1707,11 @@ rspamd_redis_learn_tokens (struct rspamd_task *task, GPtrArray *tokens, redisAsyncCommand (rt->redis, NULL, NULL, "SADD %s_keys %s", rt->stcf->symbol, rt->redis_object_expanded); + if (rt->ctx->new_schema) { + redisAsyncCommand (rt->redis, NULL, NULL, "HSET %s version 2", + rt->redis_object_expanded); + } + if (rt->stcf->clcf->flags & RSPAMD_FLAG_CLASSIFIER_INTEGER) { redis_cmd = "HINCRBY"; } diff --git a/src/libstat/learn_cache/redis_cache.c b/src/libstat/learn_cache/redis_cache.c index c3f4c6598..d43ec3665 100644 --- a/src/libstat/learn_cache/redis_cache.c +++ b/src/libstat/learn_cache/redis_cache.c @@ -95,7 +95,7 @@ rspamd_redis_cache_timeout (gint fd, short what, gpointer d) msg_err_task ("connection to redis server %s timed out", rspamd_upstream_name (rt->selected)); - rspamd_upstream_fail (rt->selected); + rspamd_upstream_fail (rt->selected, FALSE); if (rt->has_event) { rspamd_session_remove_event (task->s, rspamd_redis_cache_fin, d); @@ -147,7 +147,7 @@ rspamd_stat_cache_redis_get (redisAsyncContext *c, gpointer r, gpointer priv) rspamd_upstream_ok (rt->selected); } else { - rspamd_upstream_fail (rt->selected); + rspamd_upstream_fail (rt->selected, FALSE); } if (rt->has_event) { @@ -169,7 +169,7 @@ rspamd_stat_cache_redis_set (redisAsyncContext *c, gpointer r, gpointer priv) rspamd_upstream_ok (rt->selected); } else { - rspamd_upstream_fail (rt->selected); + rspamd_upstream_fail (rt->selected, FALSE); } if (rt->has_event) { diff --git a/src/libstat/stat_api.h b/src/libstat/stat_api.h index a4e02a591..84db8ee01 100644 --- a/src/libstat/stat_api.h +++ b/src/libstat/stat_api.h @@ -39,6 +39,17 @@ typedef struct rspamd_stat_token_s { guint flags; } rspamd_stat_token_t; +typedef struct token_node_s { + guint64 data; + guint window_idx; + guint flags; + rspamd_stat_token_t *t1; + rspamd_stat_token_t *t2; + gdouble values[]; +} rspamd_token_t; + +struct rspamd_stat_ctx; + /** * The results of statistics processing: * - error @@ -63,6 +74,14 @@ void rspamd_stat_init (struct rspamd_config *cfg, struct event_base *ev_base); void rspamd_stat_close (void); /** + * Tokenize task + * @param st_ctx + * @param task + */ +void rspamd_stat_process_tokenize (struct rspamd_stat_ctx *st_ctx, + struct rspamd_task *task); + +/** * Classify the task specified and insert symbols if needed * @param task * @param L lua state diff --git a/src/libstat/stat_config.c b/src/libstat/stat_config.c index f5483b3ca..904be726d 100644 --- a/src/libstat/stat_config.c +++ b/src/libstat/stat_config.c @@ -475,17 +475,23 @@ rspamd_stat_ctx_register_async (rspamd_stat_async_handler handler, elt->ud = d; elt->timeout = timeout; /* Enabled by default */ - elt->enabled = TRUE; - - event_set (&elt->timer_ev, -1, EV_TIMEOUT, rspamd_async_elt_on_timer, elt); - event_base_set (st_ctx->ev_base, &elt->timer_ev); - /* - * First we set timeval to zero as we want cb to be executed as - * fast as possible - */ - elt->tv.tv_sec = 0; - elt->tv.tv_usec = 0; - event_add (&elt->timer_ev, &elt->tv); + + + if (st_ctx->ev_base) { + elt->enabled = TRUE; + event_set (&elt->timer_ev, -1, EV_TIMEOUT, rspamd_async_elt_on_timer, elt); + event_base_set (st_ctx->ev_base, &elt->timer_ev); + /* + * First we set timeval to zero as we want cb to be executed as + * fast as possible + */ + elt->tv.tv_sec = 0; + elt->tv.tv_usec = 0; + event_add (&elt->timer_ev, &elt->tv); + } + else { + elt->enabled = FALSE; + } g_queue_push_tail (st_ctx->async_elts, elt); diff --git a/src/libstat/stat_internal.h b/src/libstat/stat_internal.h index cab185f7c..44f48ae5a 100644 --- a/src/libstat/stat_internal.h +++ b/src/libstat/stat_internal.h @@ -51,15 +51,6 @@ struct rspamd_statfile { gpointer bkcf; }; -typedef struct token_node_s { - guint64 data; - guint window_idx; - guint flags; - rspamd_stat_token_t *t1; - rspamd_stat_token_t *t2; - gdouble values[]; -} rspamd_token_t; - struct rspamd_stat_async_elt; typedef void (*rspamd_stat_async_handler)(struct rspamd_stat_async_elt *elt, diff --git a/src/libstat/stat_process.c b/src/libstat/stat_process.c index 4f7f4e703..f58bf6150 100644 --- a/src/libstat/stat_process.c +++ b/src/libstat/stat_process.c @@ -307,7 +307,7 @@ rspamd_stat_tokenize_parts_metadata (struct rspamd_stat_ctx *st_ctx, /* * Tokenize task using the tokenizer specified */ -static void +void rspamd_stat_process_tokenize (struct rspamd_stat_ctx *st_ctx, struct rspamd_task *task) { @@ -322,6 +322,12 @@ rspamd_stat_process_tokenize (struct rspamd_stat_ctx *st_ctx, guchar hout[rspamd_cryptobox_HASHBYTES]; gchar *b32_hout; + if (st_ctx == NULL) { + st_ctx = rspamd_stat_get_ctx (); + } + + g_assert (st_ctx != NULL); + for (i = 0; i < task->text_parts->len; i++) { part = g_ptr_array_index (task->text_parts, i); @@ -409,7 +415,10 @@ rspamd_stat_preprocess (struct rspamd_stat_ctx *st_ctx, struct rspamd_statfile *st; gpointer bk_run; - rspamd_stat_process_tokenize (st_ctx, task); + if (task->tokens == NULL) { + rspamd_stat_process_tokenize (st_ctx, task); + } + task->stat_runtimes = g_ptr_array_sized_new (st_ctx->statfiles->len); g_ptr_array_set_size (task->stat_runtimes, st_ctx->statfiles->len); rspamd_mempool_add_destructor (task->task_pool, @@ -519,12 +528,12 @@ rspamd_stat_classifiers_process (struct rspamd_stat_ctx *st_ctx, * Do not classify a message if some class is missing */ if (!(task->flags & RSPAMD_TASK_FLAG_HAS_SPAM_TOKENS)) { - msg_warn_task ("skip statistics as SPAM class is missing"); + msg_info_task ("skip statistics as SPAM class is missing"); return; } if (!(task->flags & RSPAMD_TASK_FLAG_HAS_HAM_TOKENS)) { - msg_warn_task ("skip statistics as HAM class is missing"); + msg_info_task ("skip statistics as HAM class is missing"); return; } @@ -1084,7 +1093,7 @@ rspamd_stat_has_classifier_symbols (struct rspamd_task *task, id = g_array_index (cl->statfiles_ids, gint, i); st = g_ptr_array_index (st_ctx->statfiles, id); - if (g_hash_table_lookup (mres->symbols, st->stcf->symbol)) { + if (rspamd_task_find_symbol_result (task, st->stcf->symbol)) { if (is_spam == !!st->stcf->is_spam) { msg_debug_task ("do not autolearn %s as symbol %s is already " "added", is_spam ? "spam" : "ham", st->stcf->symbol); diff --git a/src/libutil/addr.c b/src/libutil/addr.c index dee5fbbb2..a6f1adaf8 100644 --- a/src/libutil/addr.c +++ b/src/libutil/addr.c @@ -349,7 +349,6 @@ rspamd_parse_unix_path (rspamd_inet_addr_t **target, const char *src) bool has_group = false; tokens = g_strsplit_set (src, " ,", -1); - addr = rspamd_inet_addr_create (AF_UNIX); rspamd_strlcpy (addr->u.un->addr.sun_path, tokens[0], @@ -422,6 +421,7 @@ rspamd_parse_unix_path (rspamd_inet_addr_t **target, const char *src) } g_free (pwbuf); + g_strfreev (tokens); if (target) { rspamd_ip_validate_af (addr); @@ -435,6 +435,7 @@ rspamd_parse_unix_path (rspamd_inet_addr_t **target, const char *src) err: + g_strfreev (tokens); g_free (pwbuf); rspamd_inet_address_free (addr); return FALSE; @@ -492,7 +493,7 @@ gboolean rspamd_parse_inet_address_ip6 (const guchar *text, gsize len, gpointer target) { guchar t, *zero = NULL, *s, *d, *addr = target; - const guchar *p, *digit = NULL; + const guchar *p, *digit = NULL, *percent; gsize len4 = 0; guint n = 8, nibbles = 0, word = 0; @@ -512,6 +513,11 @@ rspamd_parse_inet_address_ip6 (const guchar *text, gsize len, gpointer target) p = text; } + /* Check IPv6 scope */ + if ((percent = memchr (p, '%', len)) != NULL && percent > p) { + len = percent - p; /* Ignore scope */ + } + for (/* void */; len; len--) { t = *p++; @@ -1752,9 +1758,7 @@ rspamd_inet_library_init (void) void rspamd_inet_library_destroy (void) { - if (local_addrs != NULL) { - rspamd_map_helper_destroy_radix (local_addrs); - } + /* Ugly: local_addrs will actually be freed by config object */ } gsize diff --git a/src/libutil/fstring.c b/src/libutil/fstring.c index 65f24dccc..6c51ad62e 100644 --- a/src/libutil/fstring.c +++ b/src/libutil/fstring.c @@ -16,8 +16,17 @@ #include "fstring.h" #include "str_util.h" + #ifdef WITH_JEMALLOC #include <jemalloc/jemalloc.h> +#if (JEMALLOC_VERSION_MAJOR == 3 && JEMALLOC_VERSION_MINOR >= 6) || (JEMALLOC_VERSION_MAJOR > 3) +#define HAVE_MALLOC_SIZE 1 +#define sys_alloc_size(sz) nallocx(sz, 0) +#endif +#elif defined(__APPLE__) +#include <malloc/malloc.h> +#define HAVE_MALLOC_SIZE 1 +#define sys_alloc_size(sz) malloc_good_size(sz) #endif static const gsize default_initial_size = 16; @@ -70,7 +79,7 @@ rspamd_fstring_new_init (const gchar *init, gsize len) g_error ("%s: failed to allocate %"G_GSIZE_FORMAT" bytes", G_STRLOC, real_size + sizeof (*s)); - return NULL; + abort (); } s->len = len; @@ -113,15 +122,15 @@ rspamd_fstring_free (rspamd_fstring_t *str) inline gsize rspamd_fstring_suggest_size (gsize len, gsize allocated, gsize needed_len) { - gsize newlen; + gsize newlen, optlen = 0; newlen = MAX (len + needed_len, 1 + allocated * 3 / 2); -#ifdef WITH_JEMALLOC - newlen = nallocx (newlen + sizeof (rspamd_fstring_t), 0); +#ifdef HAVE_MALLOC_SIZE + optlen = sys_alloc_size (newlen + sizeof (rspamd_fstring_t)); #endif - return newlen; + return MAX (newlen, optlen); } rspamd_fstring_t * @@ -139,8 +148,7 @@ rspamd_fstring_grow (rspamd_fstring_t *str, gsize needed_len) free (str); g_error ("%s: failed to re-allocate %"G_GSIZE_FORMAT" bytes", G_STRLOC, newlen + sizeof (*str)); - - return NULL; + abort (); } str = nptr; diff --git a/src/libutil/http.c b/src/libutil/http.c index 03dbaa7bd..2f78def47 100644 --- a/src/libutil/http.c +++ b/src/libutil/http.c @@ -844,13 +844,14 @@ rspamd_http_decrypt_message (struct rspamd_http_connection *conn, dec_len = msg->body_buf.len - rspamd_cryptobox_nonce_bytes (mode) - rspamd_cryptobox_mac_bytes (mode); - if ((nm = rspamd_pubkey_get_nm (peer_key)) == NULL) { + if ((nm = rspamd_pubkey_get_nm (peer_key, priv->local_key)) == NULL) { nm = rspamd_pubkey_calculate_nm (peer_key, priv->local_key); } if (!rspamd_cryptobox_decrypt_nm_inplace (m, dec_len, nonce, nm, m - rspamd_cryptobox_mac_bytes (mode), mode)) { - msg_err ("cannot verify encrypted message"); + msg_err ("cannot verify encrypted message, first bytes of the input: %*xs", + (gint)MIN(msg->body_buf.len, 64), msg->body_buf.begin); return -1; } @@ -1183,7 +1184,13 @@ rspamd_http_event_handler (int fd, short what, gpointer ud) http_errno_description (priv->parser.http_errno)); } - conn->error_handler (conn, err); + if (!conn->finished) { + conn->error_handler (conn, err); + } + else { + msg_err ("got error after HTTP request is finished: %e", err); + } + g_error_free (err); REF_RELEASE (pbuf); @@ -1234,7 +1241,14 @@ rspamd_http_event_handler (int fd, short what, gpointer ud) err = g_error_new (HTTP_ERROR, priv->parser.http_errno, "HTTP parser error: %s", http_errno_description (priv->parser.http_errno)); - conn->error_handler (conn, err); + + if (!conn->finished) { + conn->error_handler (conn, err); + } + else { + msg_err ("got error after HTTP request is finished: %e", err); + } + g_error_free (err); REF_RELEASE (pbuf); @@ -1689,7 +1703,7 @@ rspamd_http_connection_encrypt_message ( cnt = i; - if ((nm = rspamd_pubkey_get_nm (peer_key)) == NULL) { + if ((nm = rspamd_pubkey_get_nm (peer_key, priv->local_key)) == NULL) { nm = rspamd_pubkey_calculate_nm (peer_key, priv->local_key); } @@ -1726,11 +1740,6 @@ rspamd_http_detach_shared (struct rspamd_http_message *msg) { rspamd_fstring_t *cpy_str; - if (msg->body_buf.c.shared.shm_fd != -1) { - close (msg->body_buf.c.shared.shm_fd); - msg->body_buf.c.shared.shm_fd = -1; - } - cpy_str = rspamd_fstring_new_init (msg->body_buf.begin, msg->body_buf.len); rspamd_http_message_set_body_from_fstring_steal (msg, cpy_str); } @@ -3923,3 +3932,24 @@ rspamd_http_normalize_path_inplace (gchar *path, guint len, guint *nlen) *nlen = (o - path); } } + +void +rspamd_http_connection_disable_encryption (struct rspamd_http_connection *conn) +{ + struct rspamd_http_connection_private *priv; + + priv = conn->priv; + + if (priv) { + if (priv->local_key) { + rspamd_keypair_unref (priv->local_key); + } + if (priv->peer_key) { + rspamd_pubkey_unref (priv->peer_key); + } + + priv->local_key = NULL; + priv->peer_key = NULL; + priv->flags &= ~RSPAMD_HTTP_CONN_FLAG_ENCRYPTED; + } +}
\ No newline at end of file diff --git a/src/libutil/http.h b/src/libutil/http.h index 4ce9e0a84..c271caaa4 100644 --- a/src/libutil/http.h +++ b/src/libutil/http.h @@ -438,6 +438,8 @@ void rspamd_http_message_free (struct rspamd_http_message *msg); void rspamd_http_connection_set_max_size (struct rspamd_http_connection *conn, gsize sz); +void rspamd_http_connection_disable_encryption (struct rspamd_http_connection *conn); + /** * Increase refcount for shared file (if any) to prevent early memory unlinking * @param msg diff --git a/src/libutil/logger.c b/src/libutil/logger.c index 99c22390f..bbdc69e97 100644 --- a/src/libutil/logger.c +++ b/src/libutil/logger.c @@ -584,7 +584,7 @@ rspamd_log_encrypt_message (const gchar *begin, const gchar *end, mac = p; p += rspamd_cryptobox_mac_bytes (RSPAMD_CRYPTOBOX_MODE_25519); memcpy (p, begin, end - begin); - comp = rspamd_pubkey_get_nm (rspamd_log->pk); + comp = rspamd_pubkey_get_nm (rspamd_log->pk, rspamd_log->keypair); g_assert (comp != NULL); rspamd_cryptobox_encrypt_nm_inplace (p, end - begin, nonce, comp, mac, RSPAMD_CRYPTOBOX_MODE_25519); diff --git a/src/libutil/map.c b/src/libutil/map.c index 2873b76f6..d0fb9ff9e 100644 --- a/src/libutil/map.c +++ b/src/libutil/map.c @@ -40,21 +40,23 @@ #define MAP_RELEASE(x, t) REF_RELEASE(x) #endif -static void free_http_cbdata_common (struct http_callback_data *cbd, gboolean plan_new); +static void free_http_cbdata_common (struct http_callback_data *cbd, + gboolean plan_new); static void free_http_cbdata_dtor (gpointer p); static void free_http_cbdata (struct http_callback_data *cbd); static void rspamd_map_periodic_callback (gint fd, short what, void *ud); static void rspamd_map_schedule_periodic (struct rspamd_map *map, gboolean locked, - gboolean initial, gboolean errored); - -struct rspamd_http_map_cached_cbdata { - struct event timeout; - struct rspamd_storage_shmem *shm; - struct rspamd_map *map; - struct http_map_data *data; - guint64 gen; - time_t last_checked; -}; + gboolean initial, gboolean errored); +static gboolean read_map_file_chunks (struct rspamd_map *map, + struct map_cb_data *cbdata, + const gchar *fname, + gsize len, + goffset off); +static gboolean rspamd_map_save_http_cached_file (struct rspamd_map *map, + struct rspamd_map_backend *bk, + struct http_map_data *htdata, + const guchar *data, + gsize len); guint rspamd_map_log_id = (guint)-1; RSPAMD_CONSTRUCTOR(rspamd_map_log_init) @@ -285,8 +287,8 @@ free_http_cbdata_common (struct http_callback_data *cbd, gboolean plan_new) static void free_http_cbdata (struct http_callback_data *cbd) { - cbd->map->dtor = NULL; - cbd->map->dtor_data = NULL; + cbd->map->tmp_dtor = NULL; + cbd->map->tmp_dtor_data = NULL; free_http_cbdata_common (cbd, TRUE); } @@ -338,15 +340,21 @@ rspamd_map_cache_cb (gint fd, short what, gpointer ud) { struct rspamd_http_map_cached_cbdata *cache_cbd = ud; struct rspamd_map *map; + struct http_map_data *data; struct timeval tv; map = cache_cbd->map; + data = cache_cbd->data; if (cache_cbd->gen != cache_cbd->data->gen) { /* We have another update, so this cache element is obviously expired */ - /* Important: we do not set cache availability to zero here */ + /* + * Important!: we do not set cache availability to zero here, as there + * might be fresh cache + */ + msg_info_map ("cached data is now expired (gen mismatch %L != %L) for %s", + cache_cbd->gen, cache_cbd->data->gen, map->name); MAP_RELEASE (cache_cbd->shm, "rspamd_http_map_cached_cbdata"); - msg_info_map ("cached data is now expired (gen mismatch) for %s", map->name); event_del (&cache_cbd->timeout); g_free (cache_cbd); } @@ -361,7 +369,8 @@ rspamd_map_cache_cb (gint fd, short what, gpointer ud) event_add (&cache_cbd->timeout, &tv); } else { - g_atomic_int_set (&map->cache->available, 0); + data->cur_cache_cbd = NULL; + g_atomic_int_set (&data->cache->available, 0); MAP_RELEASE (cache_cbd->shm, "rspamd_http_map_cached_cbdata"); msg_info_map ("cached data is now expired for %s", map->name); event_del (&cache_cbd->timeout); @@ -431,6 +440,7 @@ http_map_finish (struct rspamd_http_connection *conn, struct http_callback_data *cbd = conn->ud; struct rspamd_map *map; struct rspamd_map_backend *bk; + struct http_map_data *data; struct rspamd_http_map_cached_cbdata *cache_cbd; struct timeval tv; const rspamd_ftok_t *expires_hdr, *etag_hdr; @@ -440,6 +450,7 @@ http_map_finish (struct rspamd_http_connection *conn, map = cbd->map; bk = cbd->bk; + data = bk->data.hd; if (msg->code == 200) { @@ -447,6 +458,10 @@ http_map_finish (struct rspamd_http_connection *conn, cbd->periodic->need_modify = TRUE; /* Reset the whole chain */ cbd->periodic->cur_backend = 0; + /* Reset cache, old cached data will be cleaned on timeout */ + g_atomic_int_set (&data->cache->available, 0); + data->cur_cache_cbd = NULL; + rspamd_map_periodic_callback (-1, EV_TIMEOUT, cbd->periodic); MAP_RELEASE (cbd, "http_callback_data"); @@ -652,12 +667,12 @@ read_data: /* * We know that a map is in the locked state */ - g_atomic_int_set (&map->cache->available, 1); + g_atomic_int_set (&data->cache->available, 1); /* Store cached data */ - rspamd_strlcpy (map->cache->shmem_name, cbd->shmem_data->shm_name, - sizeof (map->cache->shmem_name)); - map->cache->len = cbd->data_len; - map->cache->last_modified = cbd->data->last_modified; + rspamd_strlcpy (data->cache->shmem_name, cbd->shmem_data->shm_name, + sizeof (data->cache->shmem_name)); + data->cache->len = cbd->data_len; + data->cache->last_modified = cbd->data->last_modified; cache_cbd = g_malloc0 (sizeof (*cache_cbd)); cache_cbd->shm = cbd->shmem_data; cache_cbd->map = map; @@ -670,6 +685,7 @@ read_data: cache_cbd); event_base_set (cbd->ev_base, &cache_cbd->timeout); event_add (&cache_cbd->timeout, &tv); + data->cur_cache_cbd = cache_cbd; if (map->next_check) { rspamd_http_date_format (next_check_date, sizeof (next_check_date), @@ -734,6 +750,7 @@ read_data: rspamd_inet_address_to_string_pretty (cbd->addr), dlen, zout.pos, next_check_date); map->read_callback (out, zout.pos, &cbd->periodic->cbdata, TRUE); + rspamd_map_save_http_cached_file (map, bk, cbd->data, out, zout.pos); g_free (out); } else { @@ -741,6 +758,7 @@ read_data: cbd->bk->uri, rspamd_inet_address_to_string_pretty (cbd->addr), dlen, next_check_date); + rspamd_map_save_http_cached_file (map, bk, cbd->data, in, cbd->data_len); map->read_callback (in, cbd->data_len, &cbd->periodic->cbdata, TRUE); } @@ -819,6 +837,80 @@ err: return 0; } +static gboolean +read_map_file_chunks (struct rspamd_map *map, struct map_cb_data *cbdata, + const gchar *fname, gsize len, goffset off) +{ + gint fd; + gssize r, avail; + gsize buflen = 1024 * 1024; + gchar *pos, *bytes; + + fd = rspamd_file_xopen (fname, O_RDONLY, 0, TRUE); + + if (fd == -1) { + msg_err_map ("can't open map for buffered reading %s: %s", + fname, strerror (errno)); + return FALSE; + } + + if (lseek (fd, off, SEEK_SET) == -1) { + msg_err_map ("can't seek in map to pos %d for buffered reading %s: %s", + (gint)off, fname, strerror (errno)); + return FALSE; + } + + buflen = MIN (len, buflen); + bytes = g_malloc (buflen); + avail = buflen; + pos = bytes; + + while ((r = read (fd, pos, avail)) > 0) { + gchar *end = bytes + (pos - bytes) + r; + msg_info_map ("%s: read map chunk, %z bytes", fname, + r); + pos = map->read_callback (bytes, end - bytes, cbdata, r == len); + + if (pos && pos > bytes && pos < end) { + guint remain = end - pos; + + memmove (bytes, pos, remain); + pos = bytes + remain; + /* Need to preserve the remain */ + avail = ((gssize)buflen) - remain; + + if (avail <= 0) { + /* Try realloc, too large element */ + g_assert (buflen >= remain); + bytes = g_realloc (bytes, buflen * 2); + + pos = bytes + remain; /* Adjust */ + avail += buflen; + buflen *= 2; + } + } + else { + avail = buflen; + pos = bytes; + } + + len -= r; + } + + if (r == -1) { + msg_err_map ("can't read from map %s: %s", fname, strerror (errno)); + close (fd); + g_free (bytes); + + return FALSE; + } + + close (fd); + g_free (bytes); + + return TRUE; +} + /** * Callback for reading data from file */ @@ -826,8 +918,9 @@ static gboolean read_map_file (struct rspamd_map *map, struct file_map_data *data, struct rspamd_map_backend *bk, struct map_periodic_cbdata *periodic) { - guchar *bytes; + gchar *bytes; gsize len; + struct stat st; if (map->read_callback == NULL || map->fin_callback == NULL) { msg_err_map ("%s: bad callback for reading map file", @@ -835,7 +928,7 @@ read_map_file (struct rspamd_map *map, struct file_map_data *data, return FALSE; } - if (access (data->filename, R_OK) == -1) { + if (stat (data->filename, &st) == -1) { /* File does not exist, skipping */ if (errno != ENOENT) { msg_err_map ("%s: map file is unavailable for reading: %s", @@ -844,29 +937,41 @@ read_map_file (struct rspamd_map *map, struct file_map_data *data, return FALSE; } else { - msg_info_map ("%s: map file is not found", + msg_info_map ("%s: map file is not found; " + "it will be read automatically if created", data->filename); return TRUE; } } - bytes = rspamd_file_xmap (data->filename, PROT_READ, &len, TRUE); - - if (bytes == NULL) { - msg_err_map ("can't open map %s: %s", data->filename, strerror (errno)); - return FALSE; - } + len = st.st_size; if (bk->is_signed) { + bytes = rspamd_file_xmap (data->filename, PROT_READ, &len, TRUE); + + if (bytes == NULL) { + msg_err_map ("can't open map %s: %s", data->filename, strerror (errno)); + return FALSE; + } + if (!rspamd_map_check_file_sig (data->filename, map, bk, bytes, len)) { munmap (bytes, len); return FALSE; } + + munmap (bytes, len); } if (len > 0) { if (bk->is_compressed) { + bytes = rspamd_file_xmap (data->filename, PROT_READ, &len, TRUE); + + if (bytes == NULL) { + msg_err_map ("can't open map %s: %s", data->filename, strerror (errno)); + return FALSE; + } + ZSTD_DStream *zstream; ZSTD_inBuffer zin; ZSTD_outBuffer zout; @@ -917,18 +1022,24 @@ read_map_file (struct rspamd_map *map, struct file_map_data *data, len, zout.pos); map->read_callback (out, zout.pos, &periodic->cbdata, TRUE); g_free (out); + + munmap (bytes, len); } else { - msg_info_map ("%s: read map dat, %z bytes", data->filename, - len); - map->read_callback (bytes, len, &periodic->cbdata, TRUE); + /* Perform buffered read: fail-safe */ + if (!read_map_file_chunks (map, &periodic->cbdata, data->filename, + len, 0)) { + return FALSE; + } } } else { + /* Empty map */ map->read_callback (NULL, 0, &periodic->cbdata, TRUE); } - munmap (bytes, len); + /* Also update at the read time */ + memcpy (&data->st, &st, sizeof (struct stat)); return TRUE; } @@ -1057,7 +1168,7 @@ rspamd_map_schedule_periodic (struct rspamd_map *map, gdouble timeout; struct map_periodic_cbdata *cbd; - if (map->scheduled_check) { + if (map->scheduled_check || (map->wrk && map->wrk->wanna_die)) { /* Do not schedule check if some check is already scheduled */ return; } @@ -1189,20 +1300,24 @@ rspamd_map_read_cached (struct rspamd_map *map, struct rspamd_map_backend *bk, { gsize len; gpointer in; + struct http_map_data *data; + + data = bk->data.hd; - in = rspamd_shmem_xmap (map->cache->shmem_name, PROT_READ, &len); + in = rspamd_shmem_xmap (data->cache->shmem_name, PROT_READ, &len); if (in == NULL) { - msg_err ("cannot map cache from %s: %s", map->cache->shmem_name, + msg_err ("cannot map cache from %s: %s", data->cache->shmem_name, strerror (errno)); return FALSE; } - if (len < map->cache->len) { + if (len < data->cache->len) { msg_err ("cannot map cache from %s: bad length %z, %z expected", - map->cache->shmem_name, - len, map->cache->len); + data->cache->shmem_name, + len, data->cache->len); munmap (in, len); + return FALSE; } @@ -1269,6 +1384,168 @@ rspamd_map_read_cached (struct rspamd_map *map, struct rspamd_map_backend *bk, return TRUE; } +static gboolean +rspamd_map_has_http_cached_file (struct rspamd_map *map, + struct rspamd_map_backend *bk) +{ + gchar path[PATH_MAX]; + guchar digest[rspamd_cryptobox_HASHBYTES]; + struct rspamd_config *cfg = map->cfg; + struct stat st; + + if (cfg->maps_cache_dir == NULL || cfg->maps_cache_dir[0] == '\0') { + return FALSE; + } + + rspamd_cryptobox_hash (digest, bk->uri, strlen (bk->uri), NULL, 0); + rspamd_snprintf (path, sizeof (path), "%s%c%*xs.map", cfg->maps_cache_dir, + G_DIR_SEPARATOR, 20, digest); + + if (stat (path, &st) != -1 && st.st_size > + sizeof (struct rspamd_http_file_data)) { + return TRUE; + } + + return FALSE; +} + +static gboolean +rspamd_map_save_http_cached_file (struct rspamd_map *map, + struct rspamd_map_backend *bk, + struct http_map_data *htdata, + const guchar *data, + gsize len) +{ + gchar path[PATH_MAX]; + guchar digest[rspamd_cryptobox_HASHBYTES]; + struct rspamd_config *cfg = map->cfg; + gint fd; + struct rspamd_http_file_data header; + + if (cfg->maps_cache_dir == NULL || cfg->maps_cache_dir[0] == '\0') { + return FALSE; + } + + rspamd_cryptobox_hash (digest, bk->uri, strlen (bk->uri), NULL, 0); + rspamd_snprintf (path, sizeof (path), "%s%c%*xs.map", cfg->maps_cache_dir, + G_DIR_SEPARATOR, 20, digest); + + fd = rspamd_file_xopen (path, O_WRONLY | O_TRUNC | O_CREAT, + 00600, FALSE); + + if (fd == -1) { + return FALSE; + } + + if (!rspamd_file_lock (fd, FALSE)) { + msg_err_map ("cannot lock file %s: %s", path, strerror (errno)); + close (fd); + + return FALSE; + } + + memcpy (header.magic, rspamd_http_file_magic, sizeof (rspamd_http_file_magic)); + header.mtime = htdata->last_modified; + header.next_check = map->next_check; + header.data_off = sizeof (header); + + if (write (fd, &header, sizeof (header)) != sizeof (header)) { + msg_err_map ("cannot write file %s: %s", path, strerror (errno)); + rspamd_file_unlock (fd, FALSE); + close (fd); + + return FALSE; + } + + /* Now write the rest */ + if (write (fd, data, len) != len) { + msg_err_map ("cannot write file %s: %s", path, strerror (errno)); + rspamd_file_unlock (fd, FALSE); + close (fd); + + return FALSE; + } + + rspamd_file_unlock (fd, FALSE); + close (fd); + + msg_info_map ("saved data from %s in %s, %uz bytes", bk->uri, path, len); + + return TRUE; +} + +static gboolean +rspamd_map_read_http_cached_file (struct rspamd_map *map, + struct rspamd_map_backend *bk, + struct http_map_data *htdata, + struct map_cb_data *cbdata) +{ + gchar path[PATH_MAX]; + guchar digest[rspamd_cryptobox_HASHBYTES]; + struct rspamd_config *cfg = map->cfg; + gint fd; + struct stat st; + struct rspamd_http_file_data header; + + if (cfg->maps_cache_dir == NULL || cfg->maps_cache_dir[0] == '\0') { + return FALSE; + } + + rspamd_cryptobox_hash (digest, bk->uri, strlen (bk->uri), NULL, 0); + rspamd_snprintf (path, sizeof (path), "%s%c%*xs.map", cfg->maps_cache_dir, + G_DIR_SEPARATOR, 20, digest); + + fd = rspamd_file_xopen (path, O_RDONLY, 00600, FALSE); + + if (fd == -1) { + return FALSE; + } + + if (!rspamd_file_lock (fd, FALSE)) { + msg_err_map ("cannot lock file %s: %s", path, strerror (errno)); + close (fd); + + return FALSE; + } + + (void)fstat (fd, &st); + + if (read (fd, &header, sizeof (header)) != sizeof (header)) { + msg_err_map ("cannot read file %s: %s", path, strerror (errno)); + rspamd_file_unlock (fd, FALSE); + close (fd); + + return FALSE; + } + + if (memcmp (header.magic, rspamd_http_file_magic, + sizeof (rspamd_http_file_magic)) != 0) { + msg_err_map ("invalid magic in file %s: %s", path, strerror (errno)); + rspamd_file_unlock (fd, FALSE); + close (fd); + + return FALSE; + } + + rspamd_file_unlock (fd, FALSE); + close (fd); + + map->next_check = header.next_check; + htdata->last_modified = header.mtime; + + /* Now read file data */ + /* Perform buffered read: fail-safe */ + if (!read_map_file_chunks (map, cbdata, path, + st.st_size - header.data_off, header.data_off)) { + return FALSE; + } + + msg_info_map ("read cached data for %s from %s, %uz bytes", bk->uri, path, + st.st_size - header.data_off); + + return TRUE; +} + /** * Async HTTP callback */ @@ -1284,10 +1561,10 @@ rspamd_map_common_http_callback (struct rspamd_map *map, data = bk->data.hd; - if (g_atomic_int_get (&map->cache->available) == 1) { + if (g_atomic_int_get (&data->cache->available) == 1) { /* Read cached data */ if (check) { - if (data->last_modified < map->cache->last_modified) { + if (data->last_modified < data->cache->last_modified) { periodic->need_modify = TRUE; /* Reset the whole chain */ periodic->cur_backend = 0; @@ -1309,13 +1586,13 @@ rspamd_map_common_http_callback (struct rspamd_map *map, } else { if (map->active_http && - data->last_modified > map->cache->last_modified) { + data->last_modified > data->cache->last_modified) { goto check; } else if (rspamd_map_read_cached (map, bk, periodic, data->host)) { /* Switch to the next backend */ periodic->cur_backend++; - data->last_modified = map->cache->last_modified; + data->last_modified = data->cache->last_modified; rspamd_map_periodic_callback (-1, EV_TIMEOUT, periodic); return; @@ -1389,8 +1666,8 @@ check: MAP_RETAIN (cbd, "http_callback_data"); } - map->dtor = free_http_cbdata_dtor; - map->dtor_data = cbd; + map->tmp_dtor = free_http_cbdata_dtor; + map->tmp_dtor_data = cbd; } else { msg_warn_map ("cannot load map: DNS resolver is not initialized"); @@ -1582,45 +1859,49 @@ rspamd_map_periodic_callback (gint fd, short what, void *ud) return; } - bk = g_ptr_array_index (cbd->map->backends, cbd->cur_backend); - g_assert (bk != NULL); - - if (cbd->need_modify) { - /* Load data from the next backend */ - switch (bk->protocol) { - case MAP_PROTO_HTTP: - case MAP_PROTO_HTTPS: - rspamd_map_http_read_callback (fd, what, cbd); - break; - case MAP_PROTO_FILE: - rspamd_map_file_read_callback (fd, what, cbd); - break; - case MAP_PROTO_STATIC: - rspamd_map_static_read_callback (fd, what, cbd); - break; - } - } - else { - /* Check the next backend */ - switch (bk->protocol) { - case MAP_PROTO_HTTP: - case MAP_PROTO_HTTPS: - rspamd_map_http_check_callback (fd, what, cbd); - break; - case MAP_PROTO_FILE: - rspamd_map_file_check_callback (fd, what, cbd); - break; - case MAP_PROTO_STATIC: - rspamd_map_static_check_callback (fd, what, cbd); - break; + if (!(cbd->map->wrk && cbd->map->wrk->wanna_die)) { + bk = g_ptr_array_index (cbd->map->backends, cbd->cur_backend); + g_assert (bk != NULL); + + if (cbd->need_modify) { + /* Load data from the next backend */ + switch (bk->protocol) { + case MAP_PROTO_HTTP: + case MAP_PROTO_HTTPS: + rspamd_map_http_read_callback (fd, what, cbd); + break; + case MAP_PROTO_FILE: + rspamd_map_file_read_callback (fd, what, cbd); + break; + case MAP_PROTO_STATIC: + rspamd_map_static_read_callback (fd, what, cbd); + break; + } + } else { + /* Check the next backend */ + switch (bk->protocol) { + case MAP_PROTO_HTTP: + case MAP_PROTO_HTTPS: + rspamd_map_http_check_callback (fd, what, cbd); + break; + case MAP_PROTO_FILE: + rspamd_map_file_check_callback (fd, what, cbd); + break; + case MAP_PROTO_STATIC: + rspamd_map_static_check_callback (fd, what, cbd); + break; + } } } } /* Start watching event for all maps */ void -rspamd_map_watch (struct rspamd_config *cfg, struct event_base *ev_base, - struct rspamd_dns_resolver *resolver, gboolean active_http) +rspamd_map_watch (struct rspamd_config *cfg, + struct event_base *ev_base, + struct rspamd_dns_resolver *resolver, + struct rspamd_worker *worker, + gboolean active_http) { GList *cur = cfg->maps; struct rspamd_map *map; @@ -1630,6 +1911,7 @@ rspamd_map_watch (struct rspamd_config *cfg, struct event_base *ev_base, map = cur->data; map->ev_base = ev_base; map->r = resolver; + map->wrk = worker; if (active_http) { map->active_http = active_http; @@ -1640,7 +1922,8 @@ rspamd_map_watch (struct rspamd_config *cfg, struct event_base *ev_base, if (map->poll_timeout >= cfg->map_timeout && cfg->map_file_watch_multiplier < 1.0) { - map->poll_timeout = map->poll_timeout * cfg->map_file_watch_multiplier; + map->poll_timeout = + map->poll_timeout * cfg->map_file_watch_multiplier; } } @@ -1651,27 +1934,144 @@ rspamd_map_watch (struct rspamd_config *cfg, struct event_base *ev_base, } void +rspamd_map_preload (struct rspamd_config *cfg) +{ + GList *cur = cfg->maps; + struct rspamd_map *map; + struct rspamd_map_backend *bk; + guint i; + gboolean map_ok; + + /* First of all do synced read of data */ + while (cur) { + map = cur->data; + map_ok = TRUE; + + PTR_ARRAY_FOREACH (map->backends, i, bk) { + if (!(bk->protocol == MAP_PROTO_FILE || + bk->protocol == MAP_PROTO_STATIC)) { + + if (bk->protocol == MAP_PROTO_HTTP || + bk->protocol == MAP_PROTO_HTTPS) { + if (!rspamd_map_has_http_cached_file (map, bk)) { + + if (!map->fallback_backend) { + map_ok = FALSE; + } + break; + } + else { + continue; /* We are yet fine */ + } + } + map_ok = FALSE; + break; + } + } + + if (map_ok) { + struct map_periodic_cbdata fake_cbd; + gboolean succeed = TRUE; + + memset (&fake_cbd, 0, sizeof (fake_cbd)); + fake_cbd.cbdata.state = 0; + fake_cbd.cbdata.prev_data = *map->user_data; + fake_cbd.cbdata.cur_data = NULL; + fake_cbd.cbdata.map = map; + fake_cbd.map = map; + + PTR_ARRAY_FOREACH (map->backends, i, bk) { + fake_cbd.cur_backend = i; + + if (bk->protocol == MAP_PROTO_FILE) { + if (!read_map_file (map, bk->data.fd, bk, &fake_cbd)) { + succeed = FALSE; + break; + } + } + else if (bk->protocol == MAP_PROTO_STATIC) { + if (!read_map_static (map, bk->data.sd, bk, &fake_cbd)) { + succeed = FALSE; + break; + } + } + else if (bk->protocol == MAP_PROTO_HTTP || + bk->protocol == MAP_PROTO_HTTPS) { + if (!rspamd_map_read_http_cached_file (map, bk, bk->data.hd, + &fake_cbd.cbdata)) { + + if (map->fallback_backend) { + /* Try fallback */ + g_assert (map->fallback_backend->protocol == + MAP_PROTO_FILE); + if (!read_map_file (map, + map->fallback_backend->data.fd, + map->fallback_backend, &fake_cbd)) { + succeed = FALSE; + break; + } + } + else { + succeed = FALSE; + break; + } + } + } + else { + g_assert_not_reached (); + } + } + + if (succeed) { + map->fin_callback (&fake_cbd.cbdata); + + if (fake_cbd.cbdata.cur_data) { + *map->user_data = fake_cbd.cbdata.cur_data; + } + } + else { + msg_info_map ("preload of %s failed", map->name); + } + + } + + cur = g_list_next (cur); + } +} + +void rspamd_map_remove_all (struct rspamd_config *cfg) { struct rspamd_map *map; GList *cur; struct rspamd_map_backend *bk; + struct map_cb_data cbdata; guint i; for (cur = cfg->maps; cur != NULL; cur = g_list_next (cur)) { map = cur->data; + if (map->tmp_dtor) { + map->tmp_dtor (map->tmp_dtor_data); + } + + if (map->dtor) { + cbdata.prev_data = NULL; + cbdata.map = map; + cbdata.cur_data = *map->user_data; + + map->dtor (&cbdata); + *map->user_data = NULL; + } + for (i = 0; i < map->backends->len; i ++) { bk = g_ptr_array_index (map->backends, i); - MAP_RELEASE (bk, "rspamd_map_backend"); - } - if (g_atomic_int_compare_and_exchange (&map->cache->available, 1, 0)) { - unlink (map->cache->shmem_name); + MAP_RELEASE (bk, "rspamd_map_backend"); } - if (map->dtor) { - map->dtor (map->dtor_data); + if (map->fallback_backend) { + MAP_RELEASE (map->fallback_backend, "rspamd_map_backend"); } } @@ -1690,6 +2090,7 @@ rspamd_map_check_proto (struct rspamd_config *cfg, end = pos + strlen (pos); + /* Static check */ if (g_ascii_strcasecmp (pos, "static") == 0) { bk->protocol = MAP_PROTO_STATIC; bk->uri = g_strdup (pos); @@ -1704,46 +2105,53 @@ rspamd_map_check_proto (struct rspamd_config *cfg, return pos + 4; } - if (g_ascii_strncasecmp (pos, "sign+", sizeof ("sign+") - 1) == 0) { - bk->is_signed = TRUE; - pos += sizeof ("sign+") - 1; - } - - if (g_ascii_strncasecmp (pos, "key=", sizeof ("key=") - 1) == 0) { - pos += sizeof ("key=") - 1; - end_key = memchr (pos, '+', end - pos); + for (;;) { + if (g_ascii_strncasecmp (pos, "sign+", sizeof ("sign+") - 1) == 0) { + bk->is_signed = TRUE; + pos += sizeof ("sign+") - 1; + } + else if (g_ascii_strncasecmp (pos, "fallback+", sizeof ("fallback+") - 1) == 0) { + bk->is_fallback = TRUE; + pos += sizeof ("fallback+") - 1; + } + else if (g_ascii_strncasecmp (pos, "key=", sizeof ("key=") - 1) == 0) { + pos += sizeof ("key=") - 1; + end_key = memchr (pos, '+', end - pos); - if (end_key != NULL) { - bk->trusted_pubkey = rspamd_pubkey_from_base32 (pos, end_key - pos, - RSPAMD_KEYPAIR_SIGN, RSPAMD_CRYPTOBOX_MODE_25519); + if (end_key != NULL) { + bk->trusted_pubkey = rspamd_pubkey_from_base32 (pos, end_key - pos, + RSPAMD_KEYPAIR_SIGN, RSPAMD_CRYPTOBOX_MODE_25519); - if (bk->trusted_pubkey == NULL) { + if (bk->trusted_pubkey == NULL) { + msg_err_config ("cannot read pubkey from map: %s", + map_line); + return NULL; + } + pos = end_key + 1; + } else if (end - pos > 64) { + /* Try hex encoding */ + bk->trusted_pubkey = rspamd_pubkey_from_hex (pos, 64, + RSPAMD_KEYPAIR_SIGN, RSPAMD_CRYPTOBOX_MODE_25519); + + if (bk->trusted_pubkey == NULL) { + msg_err_config ("cannot read pubkey from map: %s", + map_line); + return NULL; + } + pos += 64; + } else { msg_err_config ("cannot read pubkey from map: %s", map_line); return NULL; } - pos = end_key + 1; - } - else if (end - pos > 64) { - /* Try hex encoding */ - bk->trusted_pubkey = rspamd_pubkey_from_hex (pos, 64, - RSPAMD_KEYPAIR_SIGN, RSPAMD_CRYPTOBOX_MODE_25519); - if (bk->trusted_pubkey == NULL) { - msg_err_config ("cannot read pubkey from map: %s", - map_line); - return NULL; + if (*pos == '+' || *pos == ':') { + pos++; } - pos += 64; } else { - msg_err_config ("cannot read pubkey from map: %s", - map_line); - return NULL; - } - - if (*pos == '+' || *pos == ':') { - pos ++; + /* No known flags */ + break; } } @@ -1776,7 +2184,6 @@ rspamd_map_check_proto (struct rspamd_config *cfg, return NULL; } - return pos; } @@ -1793,6 +2200,9 @@ rspamd_map_is_map (const gchar *map_line) else if (g_ascii_strncasecmp (map_line, "sign+", sizeof ("sign+") - 1) == 0) { ret = TRUE; } + else if (g_ascii_strncasecmp (map_line, "fallback+", sizeof ("fallback+") - 1) == 0) { + ret = TRUE; + } else if (g_ascii_strncasecmp (map_line, "file://", sizeof ("file://") - 1) == 0) { ret = TRUE; } @@ -1823,16 +2233,32 @@ rspamd_map_backend_dtor (struct rspamd_map_backend *bk) if (bk->data.sd->data) { g_free (bk->data.sd->data); } + + g_free (bk->data.sd); } break; case MAP_PROTO_HTTP: case MAP_PROTO_HTTPS: if (bk->data.hd) { - g_free (bk->data.hd->host); - g_free (bk->data.hd->path); + struct http_map_data *data = bk->data.hd; + + g_free (data->host); + g_free (data->path); + + if (data->etag) { + rspamd_fstring_free (data->etag); + } - if (bk->data.hd->etag) { - rspamd_fstring_free (bk->data.hd->etag); + if (g_atomic_int_compare_and_exchange (&data->cache->available, 1, 0)) { + if (data->cur_cache_cbd) { + MAP_RELEASE (data->cur_cache_cbd->shm, + "rspamd_http_map_cached_cbdata"); + event_del (&data->cur_cache_cbd->timeout); + g_free (data->cur_cache_cbd); + data->cur_cache_cbd = NULL; + } + + unlink (data->cache->shmem_name); } g_free (bk->data.hd); @@ -1865,6 +2291,12 @@ rspamd_map_parse_backend (struct rspamd_config *cfg, const gchar *map_line) goto err; } + if (bk->is_fallback && bk->protocol != MAP_PROTO_FILE) { + msg_err_config ("fallback backend must be file for %s", bk->uri); + + goto err; + } + end = map_line + strlen (map_line); if (end - map_line > 5) { p = end - 5; @@ -1885,9 +2317,9 @@ rspamd_map_parse_backend (struct rspamd_config *cfg, const gchar *map_line) if (access (bk->uri, R_OK) == -1) { if (errno != ENOENT) { msg_err_config ("cannot open file '%s': %s", bk->uri, strerror (errno)); - return NULL; - + goto err; } + msg_info_config ( "map '%s' is not found, but it can be loaded automatically later", bk->uri); @@ -1908,7 +2340,7 @@ rspamd_map_parse_backend (struct rspamd_config *cfg, const gchar *map_line) else { if (!(up.field_set & 1 << UF_HOST)) { msg_err_config ("cannot parse HTTP url: %s: no host", bk->uri); - return NULL; + goto err; } tok.begin = bk->uri + up.field_data[UF_HOST].off; @@ -1935,8 +2367,12 @@ rspamd_map_parse_backend (struct rspamd_config *cfg, const gchar *map_line) } } + hdata->cache = rspamd_mempool_alloc0_shared (cfg->cfg_pool, + sizeof (*hdata->cache)); + bk->data.hd = hdata; - }else if (bk->protocol == MAP_PROTO_STATIC) { + } + else if (bk->protocol == MAP_PROTO_STATIC) { sdata = g_malloc0 (sizeof (*sdata)); bk->data.sd = sdata; } @@ -2008,11 +2444,12 @@ rspamd_map_add_static_string (struct rspamd_config *cfg, struct rspamd_map * rspamd_map_add (struct rspamd_config *cfg, - const gchar *map_line, - const gchar *description, - map_cb_t read_callback, - map_fin_cb_t fin_callback, - void **user_data) + const gchar *map_line, + const gchar *description, + map_cb_t read_callback, + map_fin_cb_t fin_callback, + map_dtor_t dtor, + void **user_data) { struct rspamd_map *map; struct rspamd_map_backend *bk; @@ -2022,16 +2459,22 @@ rspamd_map_add (struct rspamd_config *cfg, return NULL; } + if (bk->is_fallback) { + msg_err_config ("cannot add map with fallback only backend: %s", bk->uri); + REF_RELEASE (bk); + + return NULL; + } + map = rspamd_mempool_alloc0 (cfg->cfg_pool, sizeof (struct rspamd_map)); map->read_callback = read_callback; map->fin_callback = fin_callback; + map->dtor = dtor; map->user_data = user_data; map->cfg = cfg; map->id = rspamd_random_uint64_fast (); map->locked = rspamd_mempool_alloc0_shared (cfg->cfg_pool, sizeof (gint)); - map->cache = - rspamd_mempool_alloc0_shared (cfg->cfg_pool, sizeof (*map->cache)); map->backends = g_ptr_array_sized_new (1); rspamd_mempool_add_destructor (cfg->cfg_pool, rspamd_ptr_array_free_hard, map->backends); @@ -2056,13 +2499,30 @@ rspamd_map_add (struct rspamd_config *cfg, return map; } +static inline void +rspamd_map_add_backend (struct rspamd_map *map, struct rspamd_map_backend *bk) +{ + if (bk->is_fallback) { + if (map->fallback_backend) { + msg_warn_map ("redefining fallback backend from %s to %s", + map->fallback_backend->uri, bk->uri); + } + + map->fallback_backend = bk; + } + else { + g_ptr_array_add (map->backends, bk); + } +} + struct rspamd_map* rspamd_map_add_from_ucl (struct rspamd_config *cfg, - const ucl_object_t *obj, - const gchar *description, - map_cb_t read_callback, - map_fin_cb_t fin_callback, - void **user_data) + const ucl_object_t *obj, + const gchar *description, + map_cb_t read_callback, + map_fin_cb_t fin_callback, + map_dtor_t dtor, + void **user_data) { ucl_object_iter_t it = NULL; const ucl_object_t *cur, *elt; @@ -2075,19 +2535,18 @@ rspamd_map_add_from_ucl (struct rspamd_config *cfg, if (ucl_object_type (obj) == UCL_STRING) { /* Just a plain string */ return rspamd_map_add (cfg, ucl_object_tostring (obj), description, - read_callback, fin_callback, user_data); + read_callback, fin_callback, dtor, user_data); } map = rspamd_mempool_alloc0 (cfg->cfg_pool, sizeof (struct rspamd_map)); map->read_callback = read_callback; map->fin_callback = fin_callback; + map->dtor = dtor; map->user_data = user_data; map->cfg = cfg; map->id = rspamd_random_uint64_fast (); map->locked = rspamd_mempool_alloc0_shared (cfg->cfg_pool, sizeof (gint)); - map->cache = - rspamd_mempool_alloc0_shared (cfg->cfg_pool, sizeof (*map->cache)); map->backends = g_ptr_array_new (); rspamd_mempool_add_destructor (cfg->cfg_pool, rspamd_ptr_array_free_hard, map->backends); @@ -2104,11 +2563,7 @@ rspamd_map_add_from_ucl (struct rspamd_config *cfg, bk = rspamd_map_parse_backend (cfg, ucl_object_tostring (cur)); if (bk != NULL) { - if (bk->protocol == MAP_PROTO_FILE) { - map->poll_timeout = (map->poll_timeout * - cfg->map_file_watch_multiplier); - } - g_ptr_array_add (map->backends, bk); + rspamd_map_add_backend (map, bk); if (!map->name) { map->name = rspamd_mempool_strdup (cfg->cfg_pool, @@ -2161,10 +2616,7 @@ rspamd_map_add_from_ucl (struct rspamd_config *cfg, bk = rspamd_map_parse_backend (cfg, ucl_object_tostring (cur)); if (bk != NULL) { - if (bk->protocol == MAP_PROTO_FILE) { - map->poll_timeout = (map->poll_timeout * cfg->map_file_watch_multiplier); - } - g_ptr_array_add (map->backends, bk); + rspamd_map_add_backend (map, bk); if (!map->name) { map->name = rspamd_mempool_strdup (cfg->cfg_pool, @@ -2191,10 +2643,7 @@ rspamd_map_add_from_ucl (struct rspamd_config *cfg, bk = rspamd_map_parse_backend (cfg, ucl_object_tostring (elt)); if (bk != NULL) { - if (bk->protocol == MAP_PROTO_FILE) { - map->poll_timeout = (map->poll_timeout * cfg->map_file_watch_multiplier); - } - g_ptr_array_add (map->backends, bk); + rspamd_map_add_backend (map, bk); if (!map->name) { map->name = rspamd_mempool_strdup (cfg->cfg_pool, @@ -2207,59 +2656,69 @@ rspamd_map_add_from_ucl (struct rspamd_config *cfg, msg_err_config ("map has no urls to be loaded: no valid backends"); goto err; } + } + else { + msg_err_config ("map has invalid type for value: %s", + ucl_object_type_to_string (ucl_object_type (obj))); + goto err; + } - PTR_ARRAY_FOREACH (map->backends, i, bk) { - if (bk->protocol == MAP_PROTO_STATIC) { - GString *map_data; - /* We need data field in ucl */ - elt = ucl_object_lookup (obj, "data"); + gboolean all_local = TRUE; - if (elt == NULL) { - msg_err_config ("map has static backend but no `data` field"); - goto err; - } + PTR_ARRAY_FOREACH (map->backends, i, bk) { + if (bk->protocol == MAP_PROTO_STATIC) { + GString *map_data; + /* We need data field in ucl */ + elt = ucl_object_lookup (obj, "data"); + if (elt == NULL) { + msg_err_config ("map has static backend but no `data` field"); + goto err; + } - if (ucl_object_type (elt) == UCL_STRING) { - map_data = g_string_sized_new (32); - if (rspamd_map_add_static_string (cfg, elt, map_data)) { - bk->data.sd->data = map_data->str; - bk->data.sd->len = map_data->len; - g_string_free (map_data, FALSE); - } - else { - g_string_free (map_data, TRUE); - msg_err_config ("map has static backend with invalid `data` field"); - goto err; - } - } - else if (ucl_object_type (elt) == UCL_ARRAY) { - map_data = g_string_sized_new (32); - it = ucl_object_iterate_new (elt); - - while ((cur = ucl_object_iterate_safe (it, true))) { - if (!rspamd_map_add_static_string (cfg, cur, map_data)) { - g_string_free (map_data, TRUE); - msg_err_config ("map has static backend with invalid " - "`data` field"); - ucl_object_iterate_free (it); - goto err; - } - } + if (ucl_object_type (elt) == UCL_STRING) { + map_data = g_string_sized_new (32); - ucl_object_iterate_free (it); + if (rspamd_map_add_static_string (cfg, elt, map_data)) { bk->data.sd->data = map_data->str; bk->data.sd->len = map_data->len; g_string_free (map_data, FALSE); } + else { + g_string_free (map_data, TRUE); + msg_err_config ("map has static backend with invalid `data` field"); + goto err; + } } + else if (ucl_object_type (elt) == UCL_ARRAY) { + map_data = g_string_sized_new (32); + it = ucl_object_iterate_new (elt); + + while ((cur = ucl_object_iterate_safe (it, true))) { + if (!rspamd_map_add_static_string (cfg, cur, map_data)) { + g_string_free (map_data, TRUE); + msg_err_config ("map has static backend with invalid " + "`data` field"); + ucl_object_iterate_free (it); + goto err; + } + } + + ucl_object_iterate_free (it); + bk->data.sd->data = map_data->str; + bk->data.sd->len = map_data->len; + g_string_free (map_data, FALSE); + } + } + else if (bk->protocol != MAP_PROTO_FILE) { + all_local = FALSE; } } - else { - msg_err_config ("map has invalid type for value: %s", - ucl_object_type_to_string (ucl_object_type (obj))); - goto err; + + if (all_local) { + map->poll_timeout = (map->poll_timeout * + cfg->map_file_watch_multiplier); } rspamd_map_calculate_hash (map); @@ -2271,6 +2730,12 @@ rspamd_map_add_from_ucl (struct rspamd_config *cfg, err: + if (map) { + PTR_ARRAY_FOREACH (map->backends, i, bk) { + MAP_RELEASE (bk, "rspamd_map_backend"); + } + } + return NULL; } diff --git a/src/libutil/map.h b/src/libutil/map.h index d10ee331a..80aa03825 100644 --- a/src/libutil/map.h +++ b/src/libutil/map.h @@ -22,6 +22,7 @@ struct map_cb_data; typedef gchar * (*map_cb_t)(gchar *chunk, gint len, struct map_cb_data *data, gboolean final); typedef void (*map_fin_cb_t)(struct map_cb_data *data); +typedef void (*map_dtor_t)(struct map_cb_data *data); typedef gboolean (*rspamd_map_traverse_cb)(gconstpointer key, gconstpointer value, gsize hits, gpointer ud); @@ -56,27 +57,38 @@ gboolean rspamd_map_is_map (const gchar *map_line); * Add map from line */ struct rspamd_map* rspamd_map_add (struct rspamd_config *cfg, - const gchar *map_line, - const gchar *description, - map_cb_t read_callback, - map_fin_cb_t fin_callback, - void **user_data); + const gchar *map_line, + const gchar *description, + map_cb_t read_callback, + map_fin_cb_t fin_callback, + map_dtor_t dtor, + void **user_data); /** * Add map from ucl */ struct rspamd_map* rspamd_map_add_from_ucl (struct rspamd_config *cfg, - const ucl_object_t *obj, - const gchar *description, - map_cb_t read_callback, - map_fin_cb_t fin_callback, - void **user_data); + const ucl_object_t *obj, + const gchar *description, + map_cb_t read_callback, + map_fin_cb_t fin_callback, + map_dtor_t dtor, + void **user_data); /** * Start watching of maps by adding events to libevent event loop */ -void rspamd_map_watch (struct rspamd_config *cfg, struct event_base *ev_base, - struct rspamd_dns_resolver *resolver, gboolean active_http); +void rspamd_map_watch (struct rspamd_config *cfg, + struct event_base *ev_base, + struct rspamd_dns_resolver *resolver, + struct rspamd_worker *worker, + gboolean active_http); + +/** + * Preloads maps where all backends are file + * @param cfg + */ +void rspamd_map_preload (struct rspamd_config *cfg); /** * Remove all maps watched (remove events) diff --git a/src/libutil/map_helpers.c b/src/libutil/map_helpers.c index d0e3e2e95..c43c23277 100644 --- a/src/libutil/map_helpers.c +++ b/src/libutil/map_helpers.c @@ -150,7 +150,7 @@ rspamd_parse_kv_list ( case map_read_key: /* read key */ /* Check here comments, eol and end of buffer */ - if (*p == '#') { + if (*p == '#' && (p == c || *(p - 1) != '\\')) { if (p - c > 0) { /* Store a single key */ MAP_STORE_KEY; @@ -813,6 +813,17 @@ rspamd_kv_list_fin (struct map_cb_data *data) } } +void +rspamd_kv_list_dtor (struct map_cb_data *data) +{ + struct rspamd_hash_map_helper *htb; + + if (data->cur_data) { + htb = (struct rspamd_hash_map_helper *)data->cur_data; + rspamd_map_helper_destroy_hash (htb); + } +} + gchar * rspamd_radix_read ( gchar * chunk, @@ -858,6 +869,17 @@ rspamd_radix_fin (struct map_cb_data *data) } } +void +rspamd_radix_dtor (struct map_cb_data *data) +{ + struct rspamd_radix_map_helper *r; + + if (data->cur_data) { + r = (struct rspamd_radix_map_helper *)data->cur_data; + rspamd_map_helper_destroy_radix (r); + } +} + static void rspamd_re_map_finalize (struct rspamd_regexp_map_helper *re_map) { @@ -1037,6 +1059,13 @@ rspamd_regexp_list_fin (struct map_cb_data *data) data->map->digest = rspamd_cryptobox_fast_hash_final (&re_map->hst); } } +void +rspamd_regexp_list_dtor (struct map_cb_data *data) +{ + if (data->cur_data) { + rspamd_map_helper_destroy_regexp (data->cur_data); + } +} #ifdef WITH_HYPERSCAN static int diff --git a/src/libutil/map_helpers.h b/src/libutil/map_helpers.h index bd933fbf2..28a5e336b 100644 --- a/src/libutil/map_helpers.h +++ b/src/libutil/map_helpers.h @@ -53,7 +53,7 @@ gchar * rspamd_radix_read ( struct map_cb_data *data, gboolean final); void rspamd_radix_fin (struct map_cb_data *data); - +void rspamd_radix_dtor (struct map_cb_data *data); /** * Kv list is an ordinal list of keys and values separated by whitespace @@ -64,6 +64,7 @@ gchar * rspamd_kv_list_read ( struct map_cb_data *data, gboolean final); void rspamd_kv_list_fin (struct map_cb_data *data); +void rspamd_kv_list_dtor (struct map_cb_data *data); /** * Regexp list is a list of regular expressions @@ -85,6 +86,7 @@ gchar * rspamd_glob_list_read_single ( struct map_cb_data *data, gboolean final); void rspamd_regexp_list_fin (struct map_cb_data *data); +void rspamd_regexp_list_dtor (struct map_cb_data *data); /** * FSM for lists parsing (support comments, blank lines and partial replies) diff --git a/src/libutil/map_private.h b/src/libutil/map_private.h index 1ea8d8040..7e0390a6c 100644 --- a/src/libutil/map_private.h +++ b/src/libutil/map_private.h @@ -23,7 +23,7 @@ #include "map.h" #include "ref.h" -typedef void (*rspamd_map_dtor) (gpointer p); +typedef void (*rspamd_map_tmp_dtor) (gpointer p); extern guint rspamd_map_log_id; #define msg_err_map(...) rspamd_default_log_function (G_LOG_LEVEL_CRITICAL, \ "map", map->tag, \ @@ -57,10 +57,33 @@ struct file_map_data { struct stat st; }; + +struct http_map_data; + +struct rspamd_http_map_cached_cbdata { + struct event timeout; + struct rspamd_storage_shmem *shm; + struct rspamd_map *map; + struct http_map_data *data; + guint64 gen; + time_t last_checked; +}; + +struct rspamd_map_cachepoint { + gint available; + gsize len; + time_t last_modified; + gchar shmem_name[256]; +}; + /** * Data specific to HTTP maps */ struct http_map_data { + /* Shared cache data */ + struct rspamd_map_cachepoint *cache; + /* Non-shared for cache owner, used to cleanup cache */ + struct rspamd_http_map_cached_cbdata *cur_cache_cbd; gchar *path; gchar *host; gchar *last_signature; @@ -88,6 +111,7 @@ struct rspamd_map_backend { enum fetch_proto protocol; gboolean is_signed; gboolean is_compressed; + gboolean is_fallback; guint32 id; struct rspamd_cryptobox_pubkey *trusted_pubkey; union rspamd_map_backend_data data; @@ -95,27 +119,23 @@ struct rspamd_map_backend { ref_entry_t ref; }; -struct rspamd_map_cachepoint { - gint available; - gsize len; - time_t last_modified; - gchar shmem_name[256]; -}; - struct rspamd_map { struct rspamd_dns_resolver *r; struct rspamd_config *cfg; GPtrArray *backends; + struct rspamd_map_backend *fallback_backend; map_cb_t read_callback; map_fin_cb_t fin_callback; + map_dtor_t dtor; void **user_data; struct event_base *ev_base; + struct rspamd_worker *wrk; gchar *description; gchar *name; guint32 id; gboolean scheduled_check; - rspamd_map_dtor dtor; - gpointer dtor_data; + rspamd_map_tmp_dtor tmp_dtor; + gpointer tmp_dtor_data; rspamd_map_traverse_function traverse_function; gpointer lua_map; gsize nelts; @@ -127,8 +147,6 @@ struct rspamd_map { gboolean active_http; /* Shared lock for temporary disabling of map reading (e.g. when this map is written by UI) */ gint *locked; - /* Shared cache data */ - struct rspamd_map_cachepoint *cache; gchar tag[MEMPOOL_UID_LEN]; }; @@ -152,6 +170,16 @@ struct map_periodic_cbdata { ref_entry_t ref; }; +static const gchar rspamd_http_file_magic[] = + {'r', 'm', 'c', 'd', '1', '0', '0', '0'}; + +struct rspamd_http_file_data { + guchar magic[sizeof (rspamd_http_file_magic)]; + goffset data_off; + gulong mtime; + gulong next_check; +}; + struct http_callback_data { struct event_base *ev_base; struct rspamd_http_connection *conn; diff --git a/src/libutil/mem_pool.c b/src/libutil/mem_pool.c index 4b4875699..e6941c8f7 100644 --- a/src/libutil/mem_pool.c +++ b/src/libutil/mem_pool.c @@ -24,6 +24,14 @@ #ifdef WITH_JEMALLOC #include <jemalloc/jemalloc.h> +#if (JEMALLOC_VERSION_MAJOR == 3 && JEMALLOC_VERSION_MINOR >= 6) || (JEMALLOC_VERSION_MAJOR > 3) +#define HAVE_MALLOC_SIZE 1 +#define sys_alloc_size(sz) nallocx(sz, 0) +#endif +#elif defined(__APPLE__) +#include <malloc/malloc.h> +#define HAVE_MALLOC_SIZE 1 +#define sys_alloc_size(sz) malloc_good_size(sz) #endif #ifdef HAVE_SCHED_YIELD @@ -159,7 +167,8 @@ static struct _pool_chain * rspamd_mempool_chain_new (gsize size, enum rspamd_mempool_chain_type pool_type) { struct _pool_chain *chain; - gsize total_size = size + sizeof (struct _pool_chain); + gsize total_size = size + sizeof (struct _pool_chain) + MEM_ALIGNMENT, + optimal_size = 0; gpointer map; g_return_val_if_fail (size > 0, NULL); @@ -173,8 +182,8 @@ rspamd_mempool_chain_new (gsize size, enum rspamd_mempool_chain_type pool_type) -1, 0); if (map == MAP_FAILED) { - msg_err ("cannot allocate %z bytes of shared memory, aborting", size + - sizeof (struct _pool_chain)); + g_error ("%s: failed to allocate %"G_GSIZE_FORMAT" bytes", + G_STRLOC, total_size); abort (); } chain = map; @@ -206,10 +215,18 @@ rspamd_mempool_chain_new (gsize size, enum rspamd_mempool_chain_type pool_type) g_atomic_int_add (&mem_pool_stat->bytes_allocated, total_size); } else { -#ifdef WITH_JEMALLOC - total_size = nallocx (total_size, 0); +#ifdef HAVE_MALLOC_SIZE + optimal_size = sys_alloc_size (total_size); #endif - map = g_malloc (total_size); + total_size = MAX (total_size, optimal_size); + map = malloc (total_size); + + if (map == NULL) { + g_error ("%s: failed to allocate %"G_GSIZE_FORMAT" bytes", + G_STRLOC, total_size); + abort (); + } + chain = map; chain->begin = ((guint8 *) chain) + sizeof (struct _pool_chain); g_atomic_int_add (&mem_pool_stat->bytes_allocated, total_size); @@ -418,7 +435,7 @@ memory_pool_alloc_common (rspamd_mempool_t * pool, gsize size, /* Allocate new chain element */ if (pool->elt_len >= size + MEM_ALIGNMENT) { pool->entry->elts[pool->entry->cur_elts].fragmentation += size; - new = rspamd_mempool_chain_new (pool->elt_len + MEM_ALIGNMENT, + new = rspamd_mempool_chain_new (pool->elt_len, pool_type); } else { @@ -426,13 +443,12 @@ memory_pool_alloc_common (rspamd_mempool_t * pool, gsize size, g_atomic_int_add (&mem_pool_stat->fragmented_size, free); pool->entry->elts[pool->entry->cur_elts].fragmentation += free; - new = rspamd_mempool_chain_new ( - size + pool->elt_len + MEM_ALIGNMENT, pool_type); + new = rspamd_mempool_chain_new (size + pool->elt_len, pool_type); } /* Connect to pool subsystem */ rspamd_mempool_append_chain (pool, new, pool_type); - /* No need to align again */ + /* No need to align again, aligned by rspamd_mempool_chain_new */ tmp = new->pos; new->pos = tmp + size; POOL_MTX_UNLOCK (); @@ -697,7 +713,7 @@ rspamd_mempool_delete (rspamd_mempool_t * pool) munmap ((void *)cur, len); } else { - g_free (cur); + free (cur); /* Not g_free as we use system allocator */ } } @@ -738,7 +754,7 @@ rspamd_mempool_cleanup_tmp (rspamd_mempool_t * pool) -((gint)cur->len)); g_atomic_int_add (&mem_pool_stat->chunks_allocated, -1); - g_free (cur); + free (cur); } g_ptr_array_free (pool->pools[RSPAMD_MEMPOOL_TMP], TRUE); diff --git a/src/libutil/printf.c b/src/libutil/printf.c index b86011b23..7ee4d35e6 100644 --- a/src/libutil/printf.c +++ b/src/libutil/printf.c @@ -105,78 +105,295 @@ rspamd_humanize_number (gchar *buf, gchar *last, gint64 num, gboolean bytes) } +static inline unsigned +rspamd_decimal_digits32 (guint32 val) +{ + static const guint32 powers_of_10[] = { + 0, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000 + }; + unsigned tmp; + +#if defined(_MSC_VER) + unsigned long r = 0; + _BitScanReverse (&r, val | 1); + tmp = (r + 1) * 1233 >> 12; +#elif defined(__GNUC__) && (__GNUC__ >= 3) + tmp = (32 - __builtin_clz (val | 1U)) * 1233 >> 12; + +#else /* Software version */ + static const unsigned debruijn_tbl[32] = { 0, 9, 1, 10, 13, 21, 2, 29, + 11, 14, 16, 18, 22, 25, 3, 30, + 8, 12, 20, 28, 15, 17, 24, 7, + 19, 27, 23, 6, 26, 5, 4, 31 }; + guint32 v = val | 1; + + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + tmp = (1 + debruijn_tbl[(v * 0x07C4ACDDU) >> 27]) * 1233 >> 12; +#endif + return tmp - (val < powers_of_10[tmp]) + 1; +} + +static inline unsigned +rspamd_decimal_digits64 (guint64 val) +{ + static const guint64 powers_of_10[] = { + 0, + 10ULL, + 100ULL, + 1000ULL, + 10000ULL, + 100000ULL, + 1000000ULL, + 10000000ULL, + 100000000ULL, + 1000000000ULL, + 10000000000ULL, + 100000000000ULL, + 1000000000000ULL, + 10000000000000ULL, + 100000000000000ULL, + 1000000000000000ULL, + 10000000000000000ULL, + 100000000000000000ULL, + 1000000000000000000ULL, + 10000000000000000000ULL + }; + unsigned tmp; + +#if defined(_MSC_VER) +#if _M_IX86 + unsigned long r = 0; + guint64 m = val | 1; + if (_BitScanReverse (&r, m >> 32)) { + r += 32; + } + else { + _BitScanReverse (&r, m & 0xFFFFFFFF); + } + tmp = (r + 1) * 1233 >> 12; +#else + unsigned long r = 0; + _BitScanReverse64 (&r, val | 1); + tmp = (r + 1) * 1233 >> 12; +#endif +#elif defined(__GNUC__) && (__GNUC__ >= 3) + tmp = (64 - __builtin_clzll (val | 1ULL)) * 1233 >> 12; +#else /* Software version */ + static const unsigned debruijn_tbl[32] = { 0, 9, 1, 10, 13, 21, 2, 29, + 11, 14, 16, 18, 22, 25, 3, 30, + 8, 12, 20, 28, 15, 17, 24, 7, + 19, 27, 23, 6, 26, 5, 4, 31 }; + guint32 v = val >> 32; + + if (v) { + v |= 1; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + tmp = 32 + debruijn_tbl[(v * 0x07C4ACDDU) >> 27]; + } + else { + v = val & 0xFFFFFFFF; + v |= 1; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + + tmp = debruijn_tbl[(v * 0x07C4ACDDU) >> 27]; + } + + + tmp = (tmp + 1) * 1233 >> 12; +#endif + + return tmp - (val < powers_of_10[tmp]) + 1; +} + +/* + * Idea from https://github.com/miloyip/itoa-benchmark: + * Uses lookup table (LUT) of digit pairs for division/modulo of 100. + * + * Mentioned in: + * https://www.slideshare.net/andreialexandrescu1/three-optimization-tips-for-c-15708507 + */ + +static const char int_lookup_table[200] = { + '0','0','0','1','0','2','0','3','0','4', + '0','5','0','6','0','7','0','8','0','9', + '1','0','1','1','1','2','1','3','1','4', + '1','5','1','6','1','7','1','8','1','9', + '2','0','2','1','2','2','2','3','2','4', + '2','5','2','6','2','7','2','8','2','9', + '3','0','3','1','3','2','3','3','3','4', + '3','5','3','6','3','7','3','8','3','9', + '4','0','4','1','4','2','4','3','4','4', + '4','5','4','6','4','7','4','8','4','9', + '5','0','5','1','5','2','5','3','5','4', + '5','5','5','6','5','7','5','8','5','9', + '6','0','6','1','6','2','6','3','6','4', + '6','5','6','6','6','7','6','8','6','9', + '7','0','7','1','7','2','7','3','7','4', + '7','5','7','6','7','7','7','8','7','9', + '8','0','8','1','8','2','8','3','8','4', + '8','5','8','6','8','7','8','8','8','9', + '9','0','9','1','9','2','9','3','9','4', + '9','5','9','6','9','7','9','8','9','9' +}; + +static inline guint +rspamd_uint32_print (guint32 in, gchar *out) +{ + guint ndigits = rspamd_decimal_digits32 (in); + gchar *p; + + p = out + ndigits - 1; + + while (in >= 100) { + unsigned idx = (in % 100) * 2; + + /* Do two digits at once */ + *p-- = int_lookup_table[idx + 1]; + *p-- = int_lookup_table[idx]; + + in /= 100; + } + + if (in < 10) { + *p = ((char)in) + '0'; + } + else { + unsigned idx = in * 2; + + *p-- = int_lookup_table[idx + 1]; + *p = int_lookup_table[idx]; + } + + return ndigits; +} + +static inline guint +rspamd_uint64_print (guint64 in, gchar *out) +{ + guint ndigits = rspamd_decimal_digits64 (in); + guint32 v32; + gchar *p; + + p = out + ndigits - 1; + + while (in >= 100000000) { + v32 = (guint32)(in % 100000000); + guint32 a, b, a1, a2, b1, b2; + + /* Initial spill */ + a = v32 / 10000; + b = v32 % 10000; + a1 = (a / 100) * 2; + a2 = (a % 100) * 2; + b1 = (b / 100) * 2; + b2 = (b % 100) * 2; + + /* Fill 8 digits at once */ + *p-- = int_lookup_table[b2 + 1]; + *p-- = int_lookup_table[b2]; + *p-- = int_lookup_table[b1 + 1]; + *p-- = int_lookup_table[b1]; + *p-- = int_lookup_table[a2 + 1]; + *p-- = int_lookup_table[a2]; + *p-- = int_lookup_table[a1 + 1]; + *p-- = int_lookup_table[a1]; + + in /= 100000000; + } + + /* Remaining 32 bit */ + v32 = (guint32)in; + + while (v32 >= 100) { + unsigned idx = (v32 % 100) << 1; + + /* Do 2 digits at once */ + *p-- = int_lookup_table[idx + 1]; + *p-- = int_lookup_table[idx]; + + v32 /= 100; + } + + if (v32 < 10) { + *p = ((char)v32) + '0'; + } + else { + unsigned idx = v32 * 2; + + *p-- = int_lookup_table[idx + 1]; + *p = int_lookup_table[idx]; + } + + return ndigits; +} + static gchar * rspamd_sprintf_num (gchar *buf, gchar *last, guint64 ui64, gchar zero, - guint hexadecimal, guint width) + guint hexadecimal, guint width) { gchar *p, temp[sizeof ("18446744073709551615")]; size_t len; - guint32 ui32; - - p = temp + sizeof(temp); if (hexadecimal == 0) { + p = temp; - if (ui64 <= G_MAXUINT32) { - - /* - * To divide 64-bit numbers and to find remainders - * on the x86 platform gcc and icc call the libc functions - * [u]divdi3() and [u]moddi3(), they call another function - * in its turn. On FreeBSD it is the qdivrem() function, - * its source code is about 170 lines of the code. - * The glibc counterpart is about 150 lines of the code. - * - * For 32-bit numbers and some divisors gcc and icc use - * a inlined multiplication and shifts. For example, - * guint "i32 / 10" is compiled to - * - * (i32 * 0xCCCCCCCD) >> 35 - */ - - ui32 = (guint32) ui64; - - do { - *--p = (gchar) (ui32 % 10 + '0'); - } while (ui32 /= 10); - - } else { - do { - *--p = (gchar) (ui64 % 10 + '0'); - } while (ui64 /= 10); + if (ui64 < G_MAXUINT32) { + len = rspamd_uint32_print ((guint32)ui64, temp); } - - } else if (hexadecimal == 1) { - + else { + len = rspamd_uint64_print (ui64, temp); + } + } + else if (hexadecimal == 1) { + p = temp + sizeof(temp); do { - - /* the "(guint32)" cast disables the BCC's warning */ *--p = _hex[(guint32) (ui64 & 0xf)]; - } while (ui64 >>= 4); - } else { /* hexadecimal == 2 */ - + len = (temp + sizeof (temp)) - p; + } + else { /* hexadecimal == 2 */ + p = temp + sizeof(temp); do { - - /* the "(guint32)" cast disables the BCC's warning */ *--p = _HEX[(guint32) (ui64 & 0xf)]; - } while (ui64 >>= 4); + + len = (temp + sizeof (temp)) - p; } /* zero or space padding */ - len = (temp + sizeof (temp)) - p; + if (len < width) { + width -= len; - while (len++ < width && buf < last) { - *buf++ = zero; + while (width-- > 0 && buf < last) { + *buf++ = zero; + } } /* number safe copy */ - len = (temp + sizeof (temp)) - p; - if (buf + len > last) { len = last - buf; } @@ -380,7 +597,7 @@ rspamd_vprintf_common (rspamd_printf_append_func func, glong written = 0, wr, slen; gint64 i64; guint64 ui64; - guint width, sign, hex, humanize, bytes, frac_width, b32; + guint width, sign, hex, humanize, bytes, frac_width, b32, b64; rspamd_fstring_t *v; rspamd_ftok_t *tok; GString *gs; @@ -415,6 +632,7 @@ rspamd_vprintf_common (rspamd_printf_append_func func, sign = 1; hex = 0; b32 = 0; + b64 = 0; bytes = 0; humanize = 0; frac_width = 0; @@ -453,6 +671,11 @@ rspamd_vprintf_common (rspamd_printf_append_func func, sign = 0; fmt++; continue; + case 'B': + b64 = 1; + sign = 0; + fmt++; + continue; case 'H': humanize = 1; bytes = 1; @@ -628,6 +851,30 @@ rspamd_vprintf_common (rspamd_printf_append_func func, buf_start = fmt; } + else if (G_UNLIKELY (b64)) { + gchar *b64buf; + gsize olen = 0; + + if (G_UNLIKELY (slen == -1)) { + if (G_LIKELY (width != 0)) { + slen = width; + } + else { + /* NULL terminated string */ + slen = strlen (p); + } + } + + b64buf = rspamd_encode_base64 (p, slen, 0, &olen); + + if (b64buf) { + RSPAMD_PRINTF_APPEND (b64buf, olen); + g_free (b64buf); + } + else { + RSPAMD_PRINTF_APPEND ("(NULL)", sizeof ("(NULL)") - 1); + } + } else { if (slen == -1) { /* NULL terminated string */ diff --git a/src/libutil/printf.h b/src/libutil/printf.h index 7cf3bd4a8..73787d3a5 100644 --- a/src/libutil/printf.h +++ b/src/libutil/printf.h @@ -42,6 +42,7 @@ * %s null-terminated string * %xs hex encoded string * %bs base32 encoded string + * %Bs base64 encoded string * %*s length and string * %Z '\0' * %N '\n' diff --git a/src/libutil/sqlite_utils.c b/src/libutil/sqlite_utils.c index d4c5e9e20..b1e33cb64 100644 --- a/src/libutil/sqlite_utils.c +++ b/src/libutil/sqlite_utils.c @@ -261,6 +261,8 @@ rspamd_sqlite3_wait (rspamd_mempool_t *pool, const gchar *lock) } } + close (fd); + if (nanosleep (&sleep_ts, NULL) == -1 && errno != EINTR) { msg_err_pool_check ("cannot sleep open lock file %s: %s", lock, strerror (errno)); @@ -358,8 +360,6 @@ rspamd_sqlite3_open_or_create (rspamd_mempool_t *pool, const gchar *path, const has_lock = TRUE; } - sqlite3_enable_shared_cache (1); - if ((rc = sqlite3_open_v2 (path, &sqlite, flags, NULL)) != SQLITE_OK) { #if SQLITE_VERSION_NUMBER >= 3008000 diff --git a/src/libutil/str_util.c b/src/libutil/str_util.c index 026e331fe..189d7a398 100644 --- a/src/libutil/str_util.c +++ b/src/libutil/str_util.c @@ -1388,10 +1388,7 @@ rspamd_substring_search_caseless (const gchar *in, gsize inlen, rspamd_substring_casecmp_func); } else if (inlen == srchlen) { - return rspamd_lc_cmp (srch, in, srchlen) == 0; - } - else { - return (-1); + return rspamd_lc_cmp (srch, in, srchlen) == 0 ? 0 : (-1); } return (-1); @@ -1455,6 +1452,10 @@ rspamd_string_find_eoh (GString *input, goffset *body_start) p++; state = got_lf; } + else if (g_ascii_isspace (*p)) { + /* We have \r<space>*, allow to stay in this state */ + p ++; + } else { p++; state = skip_char; @@ -1471,6 +1472,10 @@ rspamd_string_find_eoh (GString *input, goffset *body_start) else if (*p == '\r') { state = got_linebreak; } + else if (g_ascii_isspace (*p)) { + /* We have \n<space>*, allow to stay in this state */ + p ++; + } else { p++; state = skip_char; @@ -1487,6 +1492,10 @@ rspamd_string_find_eoh (GString *input, goffset *body_start) p++; state = got_linebreak_lf; } + else if (g_ascii_isspace (*p)) { + /* We have <linebreak><space>*, allow to stay in this state */ + p ++; + } else { p++; state = skip_char; @@ -1502,6 +1511,10 @@ rspamd_string_find_eoh (GString *input, goffset *body_start) state = got_linebreak_lf; p++; } + else if (g_ascii_isspace (*p)) { + /* We have \r\n<space>*, allow to keep in this state */ + p ++; + } else { p++; state = skip_char; @@ -1738,12 +1751,19 @@ decode: #define BITOP(a,b,op) \ ((a)[(gsize)(b)/(8*sizeof *(a))] op (gsize)1<<((gsize)(b)%(8*sizeof *(a)))) + + gsize rspamd_memcspn (const gchar *s, const gchar *e, gsize len) { gsize byteset[32 / sizeof(gsize)]; const gchar *p = s, *end = s + len; + if (!e[1]) { + for (; p < end && *p != *e; p++); + return p - s; + } + memset (byteset, 0, sizeof byteset); for (; *e && BITOP (byteset, *(guchar *)e, |=); e++); @@ -1752,6 +1772,25 @@ rspamd_memcspn (const gchar *s, const gchar *e, gsize len) return p - s; } +gsize +rspamd_memspn (const gchar *s, const gchar *e, gsize len) +{ + gsize byteset[32 / sizeof(gsize)]; + const gchar *p = s, *end = s + len; + + if (!e[1]) { + for (; p < end && *p == *e; p++); + return p - s; + } + + memset (byteset, 0, sizeof byteset); + + for (; *e && BITOP (byteset, *(guchar *)e, |=); e++); + for (; p < end && BITOP (byteset, *(guchar *)p, &); p++); + + return p - s; +} + gssize rspamd_decode_qp2047_buf (const gchar *in, gsize inlen, gchar *out, gsize outlen) diff --git a/src/libutil/str_util.h b/src/libutil/str_util.h index 45507e2be..9fa914292 100644 --- a/src/libutil/str_util.h +++ b/src/libutil/str_util.h @@ -334,6 +334,14 @@ const void *rspamd_memrchr (const void *m, gint c, gsize len); */ gsize rspamd_memcspn (const gchar *s, const gchar *e, gsize len); +/** + * Return length of memory segment starting in `s` that contains only chars from `e` + * @param s any input + * @param e zero terminated string of inclusions + * @param len length of `s` + * @return segment size + */ +gsize rspamd_memspn (const gchar *s, const gchar *e, gsize len); /* https://graphics.stanford.edu/~seander/bithacks.html#HasMoreInWord */ #define rspamd_str_hasmore(x,n) ((((x)+~0UL/255*(127-(n)))|(x))&~0UL/255*128) diff --git a/src/libutil/upstream.c b/src/libutil/upstream.c index 310a768ef..883c6f2eb 100644 --- a/src/libutil/upstream.c +++ b/src/libutil/upstream.c @@ -428,7 +428,7 @@ rspamd_upstream_set_inactive (struct upstream_list *ls, struct upstream *up) } void -rspamd_upstream_fail (struct upstream *up) +rspamd_upstream_fail (struct upstream *up, gboolean addr_failure) { gdouble error_rate, max_error_rate; gdouble sec_last, sec_cur; @@ -476,10 +476,12 @@ rspamd_upstream_fail (struct upstream *up) } } - /* Also increase count of errors for this specific address */ - if (up->addrs.addr) { - addr_elt = g_ptr_array_index (up->addrs.addr, up->addrs.cur); - addr_elt->errors ++; + if (addr_failure) { + /* Also increase count of errors for this specific address */ + if (up->addrs.addr) { + addr_elt = g_ptr_array_index (up->addrs.addr, up->addrs.cur); + addr_elt->errors++; + } } RSPAMD_UPSTREAM_UNLOCK (up->lock); diff --git a/src/libutil/upstream.h b/src/libutil/upstream.h index 84e75288d..3bc2132da 100644 --- a/src/libutil/upstream.h +++ b/src/libutil/upstream.h @@ -55,7 +55,7 @@ void rspamd_upstreams_library_config (struct rspamd_config *cfg, /** * Add an error to an upstream */ -void rspamd_upstream_fail (struct upstream *up); +void rspamd_upstream_fail (struct upstream *up, gboolean addr_failure); /** * Increase upstream successes count diff --git a/src/libutil/util.c b/src/libutil/util.c index 510b16045..6554da63b 100644 --- a/src/libutil/util.c +++ b/src/libutil/util.c @@ -852,13 +852,29 @@ setproctitle (const gchar *fmt, ...) #endif } +#if !(defined(DARWIN) || defined(SOLARIS) || defined(__APPLE__)) +static void +rspamd_title_dtor (gpointer d) +{ + gchar **env = (gchar **)d; + guint i; + + for (i = 0; env[i] != NULL; i++) { + g_free (env[i]); + } + + g_free (env); +} +#endif + /* It has to be _init function, because __attribute__((constructor)) functions gets called without arguments. */ gint -init_title (gint argc, gchar *argv[], gchar *envp[]) +init_title (struct rspamd_main *rspamd_main, + gint argc, gchar *argv[], gchar *envp[]) { #if defined(DARWIN) || defined(SOLARIS) || defined(__APPLE__) /* XXX: try to handle these OSes too */ @@ -868,45 +884,46 @@ init_title (gint argc, gchar *argv[], gchar *envp[]) gint i; for (i = 0; i < argc; ++i) { - if (!begin_of_buffer) + if (!begin_of_buffer) { begin_of_buffer = argv[i]; - if (!end_of_buffer || end_of_buffer + 1 == argv[i]) + } + if (!end_of_buffer || end_of_buffer + 1 == argv[i]) { end_of_buffer = argv[i] + strlen (argv[i]); + } } for (i = 0; envp[i]; ++i) { - if (!begin_of_buffer) + if (!begin_of_buffer) { begin_of_buffer = envp[i]; - if (!end_of_buffer || end_of_buffer + 1 == envp[i]) + } + if (!end_of_buffer || end_of_buffer + 1 == envp[i]) { end_of_buffer = envp[i] + strlen (envp[i]); + } } - if (!end_of_buffer) + if (!end_of_buffer) { return 0; + } gchar **new_environ = g_malloc ((i + 1) * sizeof (envp[0])); - if (!new_environ) - return 0; - for (i = 0; envp[i]; ++i) { - if (!(new_environ[i] = g_strdup (envp[i]))) - goto cleanup_enomem; + new_environ[i] = g_strdup (envp[i]); } - new_environ[i] = 0; + + new_environ[i] = NULL; if (program_invocation_name) { title_progname_full = g_strdup (program_invocation_name); - if (!title_progname_full) - goto cleanup_enomem; - gchar *p = strrchr (title_progname_full, '/'); - if (p) + if (p) { title_progname = p + 1; - else + } + else { title_progname = title_progname_full; + } program_invocation_name = title_progname_full; program_invocation_short_name = title_progname; @@ -916,13 +933,9 @@ init_title (gint argc, gchar *argv[], gchar *envp[]) title_buffer = begin_of_buffer; title_buffer_size = end_of_buffer - begin_of_buffer; - return 0; + rspamd_mempool_add_destructor (rspamd_main->server_pool, + rspamd_title_dtor, new_environ); -cleanup_enomem: - for (--i; i >= 0; --i) { - g_free (new_environ[i]); - } - g_free (new_environ); return 0; #endif } @@ -2081,6 +2094,9 @@ rspamd_init_libs (void) #endif SSL_CTX_set_options (ctx->ssl_ctx, ssl_options); + ctx->ssl_ctx_noverify = SSL_CTX_new (SSLv23_method ()); + SSL_CTX_set_verify (ctx->ssl_ctx_noverify, SSL_VERIFY_NONE, NULL); + SSL_CTX_set_options (ctx->ssl_ctx_noverify, ssl_options); #endif rspamd_random_seed_fast (); @@ -2176,6 +2192,19 @@ rspamd_config_libs (struct rspamd_external_libs_ctx *ctx, magic_load (ctx->libmagic, cfg->magic_file); } + rspamd_free_zstd_dictionary (ctx->in_dict); + rspamd_free_zstd_dictionary (ctx->out_dict); + + if (ctx->out_zstream) { + ZSTD_freeCStream (ctx->out_zstream); + ctx->out_zstream = NULL; + } + + if (ctx->in_zstream) { + ZSTD_freeDStream (ctx->in_zstream); + ctx->in_zstream = NULL; + } + if (cfg->zstd_input_dictionary) { ctx->in_dict = rspamd_open_zstd_dictionary ( cfg->zstd_input_dictionary); @@ -2282,6 +2311,7 @@ rspamd_deinit_libs (struct rspamd_external_libs_ctx *ctx) EVP_cleanup (); ERR_free_strings (); SSL_CTX_free (ctx->ssl_ctx); + SSL_CTX_free (ctx->ssl_ctx_noverify); #endif rspamd_inet_library_destroy (); rspamd_free_zstd_dictionary (ctx->in_dict); @@ -2943,4 +2973,44 @@ rspamd_glob_path (const gchar *dir, } return res; -}
\ No newline at end of file +} + +double +rspamd_set_counter (struct rspamd_counter_data *cd, gdouble value) +{ + gdouble cerr; + + /* Cumulative moving average using per-process counter data */ + if (cd->number == 0) { + cd->mean = 0; + cd->stddev = 0; + } + + cd->mean += (value - cd->mean) / (gdouble)(++cd->number); + cerr = (value - cd->mean) * (value - cd->mean); + cd->stddev += (cerr - cd->stddev) / (gdouble)(cd->number); + + return cd->mean; +} + +double +rspamd_set_counter_ema (struct rspamd_counter_data *cd, + gdouble value, + gdouble alpha) +{ + gdouble diff, incr; + + /* Cumulative moving average using per-process counter data */ + if (cd->number == 0) { + cd->mean = 0; + cd->stddev = 0; + } + + diff = value - cd->mean; + incr = diff * alpha; + cd->mean += incr; + cd->stddev = (1 - alpha) * (cd->stddev + diff * incr); + cd->number ++; + + return cd->mean; +} diff --git a/src/libutil/util.h b/src/libutil/util.h index 96e85af30..4338d5557 100644 --- a/src/libutil/util.h +++ b/src/libutil/util.h @@ -116,7 +116,7 @@ void rspamd_pass_signal (GHashTable *, gint ); /* * Process title utility functions */ -gint init_title (gint argc, gchar *argv[], gchar *envp[]); +gint init_title (struct rspamd_main *, gint argc, gchar *argv[], gchar *envp[]); gint setproctitle (const gchar *fmt, ...); #endif @@ -511,4 +511,30 @@ GPtrArray *rspamd_glob_path (const gchar *dir, gboolean recursive, GError **err); +struct rspamd_counter_data { + gdouble mean; + gdouble stddev; + guint64 number; +}; + +/** + * Sets counter's data using exponential moving average + * @param cd counter + * @param value new counter value + * @param alpha decay coefficient (0..1) + * @return new counter value + */ +double rspamd_set_counter_ema (struct rspamd_counter_data *cd, + gdouble value, + gdouble alpha); + +/** + * Sets counter's data using flat moving average + * @param cd counter + * @param value new counter value + * @return new counter value + */ +double rspamd_set_counter (struct rspamd_counter_data *cd, + gdouble value); + #endif diff --git a/src/lua/lua_common.h b/src/lua/lua_common.h index 6c64c76d4..838e0fe7a 100644 --- a/src/lua/lua_common.h +++ b/src/lua/lua_common.h @@ -208,18 +208,22 @@ struct rspamd_lua_ip * lua_check_ip (lua_State * L, gint pos); struct rspamd_lua_text * lua_check_text (lua_State * L, gint pos); +enum rspamd_lua_task_header_type { + RSPAMD_TASK_HEADER_PUSH_SIMPLE = 0, + RSPAMD_TASK_HEADER_PUSH_RAW, + RSPAMD_TASK_HEADER_PUSH_FULL, + RSPAMD_TASK_HEADER_PUSH_COUNT, +}; gint rspamd_lua_push_header (lua_State *L, - struct rspamd_mime_header *h, - gboolean full, - gboolean raw); + struct rspamd_mime_header *h, + enum rspamd_lua_task_header_type how); /** * Push specific header to lua */ gint rspamd_lua_push_header_array (lua_State *L, - GPtrArray *hdrs, - gboolean full, - gboolean raw); + GPtrArray *hdrs, + enum rspamd_lua_task_header_type how); /** * Check for task at the specified position diff --git a/src/lua/lua_config.c b/src/lua/lua_config.c index 37d122402..2093cbe01 100644 --- a/src/lua/lua_config.c +++ b/src/lua/lua_config.c @@ -17,6 +17,7 @@ #include "libmime/message.h" #include "libutil/expression.h" #include "libserver/composites.h" +#include "libmime/lang_detection.h" #include "lua/lua_map.h" #include "utlist.h" #include <math.h> @@ -204,6 +205,7 @@ LUA_FUNCTION_DEF (config, get_classifier); * + `empty` if symbol can be called for empty messages * + `skip` if symbol should be skipped now * + `nostat` if symbol should be excluded from stat tokens + * + `trivial` symbol is trivial (e.g. no network requests) * - `parent`: id of parent symbol (useful for virtual symbols) * * @return {number} id of symbol registered @@ -710,6 +712,22 @@ LUA_FUNCTION_DEF (config, parse_rcl); */ LUA_FUNCTION_DEF (config, init_modules); +/*** + * @method rspamd_config:init_subsystem(str) + * Initialize config subsystem from a comma separated list: + * - `modules` - init modules + * - `langdet` - language detector + * - TODO: add more + */ +LUA_FUNCTION_DEF (config, init_subsystem); + +/*** + * @method rspamd_config:get_tld_path() + * Returns path to TLD file + * @return {string} path to tld file + */ +LUA_FUNCTION_DEF (config, get_tld_path); + static const struct luaL_reg configlib_m[] = { LUA_INTERFACE_DEF (config, get_module_opt), LUA_INTERFACE_DEF (config, get_mempool), @@ -771,6 +789,8 @@ static const struct luaL_reg configlib_m[] = { LUA_INTERFACE_DEF (config, load_ucl), LUA_INTERFACE_DEF (config, parse_rcl), LUA_INTERFACE_DEF (config, init_modules), + LUA_INTERFACE_DEF (config, init_subsystem), + LUA_INTERFACE_DEF (config, get_tld_path), {"__tostring", rspamd_lua_class_tostring}, {"__newindex", lua_config_newindex}, {NULL, NULL} @@ -1549,6 +1569,9 @@ lua_parse_symbol_flags (const gchar *str) if (strstr (str, "squeezed") != NULL) { ret |= SYMBOL_TYPE_SQUEEZED; } + if (strstr (str, "trivial") != NULL) { + ret |= SYMBOL_TYPE_TRIVIAL; + } } return ret; @@ -1969,7 +1992,7 @@ lua_config_register_dependency (lua_State * L) skip_squeeze = lua_toboolean (L, 4); } - if (child != NULL && child != NULL) { + if (child != NULL && parent != NULL) { if (skip_squeeze || !rspamd_lua_squeeze_dependency (L, cfg, child, parent)) { rspamd_symbols_cache_add_delayed_dependency (cfg->cache, child, @@ -2249,7 +2272,8 @@ lua_config_add_composite (lua_State * L) msg_warn_config ("composite %s is redefined", name); new = FALSE; } - composite = rspamd_mempool_alloc (cfg->cfg_pool, + + composite = rspamd_mempool_alloc0 (cfg->cfg_pool, sizeof (struct rspamd_composite)); composite->expr = expr; composite->id = g_hash_table_size (cfg->composite_symbols); @@ -3412,6 +3436,56 @@ lua_config_init_modules (lua_State *L) } static gint +lua_config_init_subsystem (lua_State *L) +{ + struct rspamd_config *cfg = lua_check_config (L, 1); + const gchar *subsystem = luaL_checkstring (L, 2); + gchar **parts; + guint nparts, i; + + if (cfg != NULL && subsystem != NULL) { + parts = g_strsplit_set (subsystem, ";,", -1); + nparts = g_strv_length (parts); + + for (i = 0; i < nparts; i ++) { + if (strcmp (parts[i], "filters") == 0) { + rspamd_lua_post_load_config (cfg); + rspamd_init_filters (cfg, FALSE); + } + else if (strcmp (parts[i], "langdet") == 0) { + cfg->lang_det = rspamd_language_detector_init (cfg); + } + else if (strcmp (parts[i], "stat") == 0) { + rspamd_stat_init (cfg, NULL); + } + else { + return luaL_error (L, "invalid param: %s", parts[i]); + } + } + } + else { + return luaL_error (L, "invalid arguments"); + } + + return 0; +} + +static gint +lua_config_get_tld_path (lua_State *L) +{ + struct rspamd_config *cfg = lua_check_config (L, 1); + + if (cfg != NULL) { + lua_pushstring (L, cfg->tld_file); + } + else { + return luaL_error (L, "invalid arguments"); + } + + return 1; +} + +static gint lua_monitored_alive (lua_State *L) { struct rspamd_monitored *m = lua_check_monitored (L, 1); diff --git a/src/lua/lua_cryptobox.c b/src/lua/lua_cryptobox.c index 6fa2aa997..5f4908adf 100644 --- a/src/lua/lua_cryptobox.c +++ b/src/lua/lua_cryptobox.c @@ -1530,23 +1530,40 @@ lua_cryptobox_sign_file (lua_State *L) } /*** - * @function rspamd_cryptobox.encrypt_memory(kp, data) - * Encrypt data using specified keypair - * @param {keypair} kp keypair to use + * @function rspamd_cryptobox.encrypt_memory(kp, data[, nist=false]) + * Encrypt data using specified keypair/pubkey + * @param {keypair|string} kp keypair or pubkey in base32 to use * @param {string|text} data * @return {rspamd_text} encrypted text */ static gint lua_cryptobox_encrypt_memory (lua_State *L) { - struct rspamd_cryptobox_keypair *kp; + struct rspamd_cryptobox_keypair *kp = NULL; + struct rspamd_cryptobox_pubkey *pk = NULL; const gchar *data; - guchar *out; + guchar *out = NULL; struct rspamd_lua_text *t, *res; - gsize len = 0, outlen; + gsize len = 0, outlen = 0; GError *err = NULL; - kp = lua_check_cryptobox_keypair (L, 1); + if (lua_type (L, 1) == LUA_TUSERDATA) { + if (rspamd_lua_check_udata_maybe (L, 1, "rspamd{cryptobox_keypair}")) { + kp = lua_check_cryptobox_keypair (L, 1); + } + else if (rspamd_lua_check_udata_maybe (L, 1, "rspamd{cryptobox_pubkey}")) { + pk = lua_check_cryptobox_pubkey (L, 1); + } + } + else if (lua_type (L, 1) == LUA_TSTRING) { + const gchar *b32; + gsize blen; + + b32 = lua_tolstring (L, 1, &blen); + pk = rspamd_pubkey_from_base32 (b32, blen, RSPAMD_KEYPAIR_KEX, + lua_toboolean (L, 3) ? + RSPAMD_CRYPTOBOX_MODE_NIST : RSPAMD_CRYPTOBOX_MODE_25519); + } if (lua_isuserdata (L, 2)) { t = lua_check_text (L, 2); @@ -1563,15 +1580,25 @@ lua_cryptobox_encrypt_memory (lua_State *L) } - if (!kp || !data) { + if (!(kp || pk) || !data) { return luaL_error (L, "invalid arguments"); } - if (!rspamd_keypair_encrypt (kp, data, len, &out, &outlen, &err)) { - gint ret = luaL_error (L, "cannot encrypt data: %s", err->message); - g_error_free (err); + if (kp) { + if (!rspamd_keypair_encrypt (kp, data, len, &out, &outlen, &err)) { + gint ret = luaL_error (L, "cannot encrypt data: %s", err->message); + g_error_free (err); + + return ret; + } + } + else if (pk) { + if (!rspamd_pubkey_encrypt (pk, data, len, &out, &outlen, &err)) { + gint ret = luaL_error (L, "cannot encrypt data: %s", err->message); + g_error_free (err); - return ret; + return ret; + } } res = lua_newuserdata (L, sizeof (*res)); @@ -1584,39 +1611,68 @@ lua_cryptobox_encrypt_memory (lua_State *L) } /*** - * @function rspamd_cryptobox.encrypt_file(kp, filename) - * Encrypt data using specified keypair - * @param {keypair} kp keypair to use + * @function rspamd_cryptobox.encrypt_file(kp|pk_string, filename[, nist=false]) + * Encrypt data using specified keypair/pubkey + * @param {keypair|string} kp keypair or pubkey in base32 to use * @param {string} filename * @return {rspamd_text} encrypted text */ static gint lua_cryptobox_encrypt_file (lua_State *L) { - struct rspamd_cryptobox_keypair *kp; + struct rspamd_cryptobox_keypair *kp = NULL; + struct rspamd_cryptobox_pubkey *pk = NULL; const gchar *filename; gchar *data; - guchar *out; + guchar *out = NULL; struct rspamd_lua_text *res; - gsize len = 0, outlen; + gsize len = 0, outlen = 0; GError *err = NULL; - kp = lua_check_cryptobox_keypair (L, 1); + if (lua_type (L, 1) == LUA_TUSERDATA) { + if (rspamd_lua_check_udata_maybe (L, 1, "rspamd{cryptobox_keypair}")) { + kp = lua_check_cryptobox_keypair (L, 1); + } + else if (rspamd_lua_check_udata_maybe (L, 1, "rspamd{cryptobox_pubkey}")) { + pk = lua_check_cryptobox_pubkey (L, 1); + } + } + else if (lua_type (L, 1) == LUA_TSTRING) { + const gchar *b32; + gsize blen; + + b32 = lua_tolstring (L, 1, &blen); + pk = rspamd_pubkey_from_base32 (b32, blen, RSPAMD_KEYPAIR_KEX, + lua_toboolean (L, 3) ? + RSPAMD_CRYPTOBOX_MODE_NIST : RSPAMD_CRYPTOBOX_MODE_25519); + } + filename = luaL_checkstring (L, 2); data = rspamd_file_xmap (filename, PROT_READ, &len, TRUE); - - if (!kp || !data) { + if (!(kp || pk) || !data) { return luaL_error (L, "invalid arguments"); } - if (!rspamd_keypair_encrypt (kp, data, len, &out, &outlen, &err)) { - gint ret = luaL_error (L, "cannot encrypt file %s: %s", filename, - err->message); - g_error_free (err); - munmap (data, len); + if (kp) { + if (!rspamd_keypair_encrypt (kp, data, len, &out, &outlen, &err)) { + gint ret = luaL_error (L, "cannot encrypt file %s: %s", filename, + err->message); + g_error_free (err); + munmap (data, len); - return ret; + return ret; + } + } + else if (pk) { + if (!rspamd_pubkey_encrypt (pk, data, len, &out, &outlen, &err)) { + gint ret = luaL_error (L, "cannot encrypt file %s: %s", filename, + err->message); + g_error_free (err); + munmap (data, len); + + return ret; + } } res = lua_newuserdata (L, sizeof (*res)); @@ -1630,7 +1686,7 @@ lua_cryptobox_encrypt_file (lua_State *L) } /*** - * @function rspamd_cryptobox.decrypt_memory(kp, data) + * @function rspamd_cryptobox.decrypt_memory(kp, data[, nist = false]) * Encrypt data using specified keypair * @param {keypair} kp keypair to use * @param {string} data diff --git a/src/lua/lua_html.c b/src/lua/lua_html.c index bfc411b94..dc058745c 100644 --- a/src/lua/lua_html.c +++ b/src/lua/lua_html.c @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include <src/libserver/html.h> #include "lua_common.h" #include "message.h" #include "html.h" @@ -92,6 +93,7 @@ LUA_FUNCTION_DEF (html, get_images); * `color` - a triplet (r g b) for font color * `bgcolor` - a triplet (r g b) for background color * `style` - rspamd{text} with the full style description + * `font_size` - font size * @return {table} table of blocks in html part */ LUA_FUNCTION_DEF (html, get_blocks); @@ -316,38 +318,39 @@ static void lua_html_push_block (lua_State *L, struct html_block *bl) { struct rspamd_lua_text *t; - struct html_tag **ptag; - lua_createtable (L, 0, 5); + lua_createtable (L, 0, 6); if (bl->tag) { lua_pushstring (L, "tag"); - ptag = lua_newuserdata (L, sizeof (gpointer)); - *ptag = bl->tag; - rspamd_lua_setclass (L, "rspamd{html_tag}", -1); + lua_pushlstring (L, bl->tag->name.start, bl->tag->name.len); lua_settable (L, -3); } if (bl->font_color.valid) { lua_pushstring (L, "color"); - lua_newtable (L); + lua_createtable (L, 4, 0); lua_pushnumber (L, bl->font_color.d.comp.r); lua_rawseti (L, -2, 1); lua_pushnumber (L, bl->font_color.d.comp.g); lua_rawseti (L, -2, 2); lua_pushnumber (L, bl->font_color.d.comp.b); lua_rawseti (L, -2, 3); + lua_pushnumber (L, bl->font_color.d.comp.alpha); + lua_rawseti (L, -2, 4); lua_settable (L, -3); } if (bl->background_color.valid) { lua_pushstring (L, "bgcolor"); - lua_newtable (L); + lua_createtable (L, 4, 0); lua_pushnumber (L, bl->background_color.d.comp.r); lua_rawseti (L, -2, 1); lua_pushnumber (L, bl->background_color.d.comp.g); lua_rawseti (L, -2, 2); lua_pushnumber (L, bl->background_color.d.comp.b); lua_rawseti (L, -2, 3); + lua_pushnumber (L, bl->background_color.d.comp.alpha); + lua_rawseti (L, -2, 4); lua_settable (L, -3); } @@ -364,6 +367,10 @@ lua_html_push_block (lua_State *L, struct html_block *bl) lua_pushstring (L, "visible"); lua_pushboolean (L, bl->visible); lua_settable (L, -3); + + lua_pushstring (L, "font_size"); + lua_pushnumber (L, bl->font_size); + lua_settable (L, -3); } static gint @@ -485,7 +492,7 @@ lua_html_foreach_tag (lua_State *L) lua_pop (L, 1); } - if (hc && g_hash_table_size (ud.tags) > 0 && lua_isfunction (L, 3)) { + if (hc && (ud.any || g_hash_table_size (ud.tags) > 0) && lua_isfunction (L, 3)) { if (hc->html_tags) { lua_pushvalue (L, 3); diff --git a/src/lua/lua_http.c b/src/lua/lua_http.c index 2e331f57d..87244dd55 100644 --- a/src/lua/lua_http.c +++ b/src/lua/lua_http.c @@ -69,13 +69,12 @@ struct lua_http_cbdata { rspamd_inet_addr_t *addr; gchar *mime_type; gchar *host; + gchar *auth; const gchar *url; gsize max_size; gint flags; gint fd; gint cbref; - gint bodyref; - gboolean gzip; }; static const int default_http_timeout = 5000; @@ -123,6 +122,10 @@ lua_http_fin (gpointer arg) g_free (cbd->host); } + if (cbd->auth) { + g_free (cbd->auth); + } + if (cbd->local_kp) { rspamd_keypair_unref (cbd->local_kp); } @@ -245,7 +248,8 @@ lua_http_make_connection (struct lua_http_cbdata *cbd) RSPAMD_HTTP_CLIENT_SIMPLE, RSPAMD_HTTP_CLIENT, NULL, - cbd->cfg->libs_ctx->ssl_ctx); + (cbd->flags & RSPAMD_LUA_HTTP_FLAG_NOVERIFY) ? + cbd->cfg->libs_ctx->ssl_ctx_noverify : cbd->cfg->libs_ctx->ssl_ctx); } else { cbd->conn = rspamd_http_connection_new (NULL, @@ -274,6 +278,11 @@ lua_http_make_connection (struct lua_http_cbdata *cbd) rspamd_http_connection_set_max_size (cbd->conn, cbd->max_size); } + if (cbd->auth) { + rspamd_http_message_add_header (cbd->msg, "Authorization", + cbd->auth); + } + rspamd_http_connection_write_message (cbd->conn, cbd->msg, cbd->host, cbd->mime_type, cbd, fd, &cbd->tv, cbd->ev_base); @@ -381,6 +390,7 @@ lua_http_request (lua_State *L) gdouble timeout = default_http_timeout; gint flags = 0; gchar *mime_type = NULL; + gchar *auth = NULL; gsize max_size = 0; gboolean gzip = FALSE; @@ -616,6 +626,38 @@ lua_http_request (lua_State *L) } lua_pop (L, 1); + + lua_pushstring (L, "user"); + lua_gettable (L, 1); + + if (lua_type (L, -1) == LUA_TSTRING) { + const gchar *user = lua_tostring (L, -1); + + lua_pushstring (L, "password"); + lua_gettable (L, 1); + + if (lua_type (L, -1) == LUA_TSTRING) { + const gchar *password = lua_tostring (L, -1); + gchar *tmpbuf; + gsize tlen; + + tlen = strlen (user) + strlen (password) + 1; + tmpbuf = g_malloc (tlen + 1); + rspamd_snprintf (tmpbuf, tlen + 1, "%s:%s", user, password); + tlen *= 2; + tlen += sizeof ("Basic ") - 1; + auth = g_malloc (tlen + 1); + rspamd_snprintf (auth, tlen + 1, "Basic %Bs", tmpbuf); + g_free (tmpbuf); + } + else { + msg_warn ("HTTP user must have password, disabling auth"); + } + + lua_pop (L, 1); /* password */ + } + + lua_pop (L, 1); /* username */ } else { msg_err ("http request has bad params"); @@ -638,6 +680,7 @@ lua_http_request (lua_State *L) cbd->flags = flags; cbd->max_size = max_size; cbd->url = url; + cbd->auth = auth; if (msg->host) { cbd->host = rspamd_fstring_cstr (msg->host); @@ -653,11 +696,6 @@ lua_http_request (lua_State *L) rspamd_http_message_set_body_from_fstring_steal (msg, body); } - if (gzip) { - cbd->gzip = TRUE; - /* TODO: Add client support for gzip */ - } - if (session) { cbd->session = session; rspamd_session_add_event (session, diff --git a/src/lua/lua_logger.c b/src/lua/lua_logger.c index f112b4dfc..a1297563b 100644 --- a/src/lua/lua_logger.c +++ b/src/lua/lua_logger.c @@ -319,6 +319,7 @@ lua_logger_out_userdata (lua_State *L, gint pos, gchar *outbuf, gsize len) { gint r, top; const gchar *str = NULL; + gboolean converted_to_str = FALSE; top = lua_gettop (L); @@ -348,6 +349,10 @@ lua_logger_out_userdata (lua_State *L, gint pos, gchar *outbuf, gsize len) } str = lua_tostring (L, -1); + + if (str) { + converted_to_str = TRUE; + } } else { lua_pushstring (L, "class"); @@ -362,7 +367,14 @@ lua_logger_out_userdata (lua_State *L, gint pos, gchar *outbuf, gsize len) str = lua_tostring (L, -1); } - r = rspamd_snprintf (outbuf, len + 1, "%s(%p)", str, lua_touserdata (L, pos)); + if (converted_to_str) { + r = rspamd_snprintf (outbuf, len + 1, "%s", str); + } + else { + /* Print raw pointer */ + r = rspamd_snprintf (outbuf, len + 1, "%s(%p)", str, lua_touserdata (L, pos)); + } + lua_settop (L, top); return r; diff --git a/src/lua/lua_map.c b/src/lua/lua_map.c index 2d2a098d2..d3fc8edb9 100644 --- a/src/lua/lua_map.c +++ b/src/lua/lua_map.c @@ -156,6 +156,7 @@ lua_config_add_radix_map (lua_State *L) if ((m = rspamd_map_add (cfg, map_line, description, rspamd_radix_read, rspamd_radix_fin, + rspamd_radix_dtor, (void **)&map->data.radix)) == NULL) { msg_warn_config ("invalid radix map %s", map_line); lua_pushnil (L); @@ -211,6 +212,7 @@ lua_config_radix_from_config (lua_State *L) if ((m = rspamd_map_add_from_ucl (cfg, fake_obj, "static radix map", rspamd_radix_read, rspamd_radix_fin, + rspamd_radix_dtor, (void **)&map->data.radix)) == NULL) { msg_err_config ("invalid radix map static"); lua_pushnil (L); @@ -270,15 +272,18 @@ lua_config_radix_from_ucl (lua_State *L) if ((m = rspamd_map_add_from_ucl (cfg, fake_obj, "static radix map", rspamd_radix_read, rspamd_radix_fin, + rspamd_radix_dtor, (void **)&map->data.radix)) == NULL) { msg_err_config ("invalid radix map static"); lua_pushnil (L); ucl_object_unref (fake_obj); + ucl_object_unref (obj); return 1; } ucl_object_unref (fake_obj); + ucl_object_unref (obj); pmap = lua_newuserdata (L, sizeof (void *)); map->map = m; m->lua_map = map; @@ -311,6 +316,7 @@ lua_config_add_hash_map (lua_State *L) if ((m = rspamd_map_add (cfg, map_line, description, rspamd_kv_list_read, rspamd_kv_list_fin, + rspamd_kv_list_dtor, (void **)&map->data.hash)) == NULL) { msg_warn_config ("invalid set map %s", map_line); lua_pushnil (L); @@ -349,6 +355,7 @@ lua_config_add_kv_map (lua_State *L) if ((m = rspamd_map_add (cfg, map_line, description, rspamd_kv_list_read, rspamd_kv_list_fin, + rspamd_kv_list_dtor, (void **)&map->data.hash)) == NULL) { msg_warn_config ("invalid hash map %s", map_line); lua_pushnil (L); @@ -441,6 +448,23 @@ lua_map_fin (struct map_cb_data *data) cbdata->data = rspamd_fstring_assign (cbdata->data, "", 0); } +static void +lua_map_dtor (struct map_cb_data *data) +{ + struct lua_map_callback_data *cbdata; + + if (data->cur_data) { + cbdata = (struct lua_map_callback_data *)data->cur_data; + if (cbdata->ref != -1) { + luaL_unref (cbdata->L, LUA_REGISTRYINDEX, cbdata->ref); + } + + if (cbdata->data) { + rspamd_fstring_free (cbdata->data); + } + } +} + gint lua_config_add_map (lua_State *L) { @@ -488,7 +512,9 @@ lua_config_add_map (lua_State *L) cbdata->ref = cbidx; if ((m = rspamd_map_add_from_ucl (cfg, map_obj, description, - lua_map_read, lua_map_fin, + lua_map_read, + lua_map_fin, + lua_map_dtor, (void **)&map->data.cbdata)) == NULL) { if (cbidx != -1) { @@ -513,6 +539,7 @@ lua_config_add_map (lua_State *L) if ((m = rspamd_map_add_from_ucl (cfg, map_obj, description, rspamd_kv_list_read, rspamd_kv_list_fin, + rspamd_kv_list_dtor, (void **)&map->data.hash)) == NULL) { lua_pushnil (L); ucl_object_unref (map_obj); @@ -529,6 +556,7 @@ lua_config_add_map (lua_State *L) if ((m = rspamd_map_add_from_ucl (cfg, map_obj, description, rspamd_kv_list_read, rspamd_kv_list_fin, + rspamd_kv_list_dtor, (void **)&map->data.hash)) == NULL) { lua_pushnil (L); ucl_object_unref (map_obj); @@ -545,6 +573,7 @@ lua_config_add_map (lua_State *L) if ((m = rspamd_map_add_from_ucl (cfg, map_obj, description, rspamd_radix_read, rspamd_radix_fin, + rspamd_radix_dtor, (void **)&map->data.radix)) == NULL) { lua_pushnil (L); ucl_object_unref (map_obj); @@ -561,6 +590,7 @@ lua_config_add_map (lua_State *L) if ((m = rspamd_map_add_from_ucl (cfg, map_obj, description, rspamd_regexp_list_read_single, rspamd_regexp_list_fin, + rspamd_regexp_list_dtor, (void **) &map->data.re_map)) == NULL) { lua_pushnil (L); ucl_object_unref (map_obj); @@ -577,6 +607,7 @@ lua_config_add_map (lua_State *L) if ((m = rspamd_map_add_from_ucl (cfg, map_obj, description, rspamd_regexp_list_read_multiple, rspamd_regexp_list_fin, + rspamd_regexp_list_dtor, (void **) &map->data.re_map)) == NULL) { lua_pushnil (L); ucl_object_unref (map_obj); @@ -593,6 +624,7 @@ lua_config_add_map (lua_State *L) if ((m = rspamd_map_add_from_ucl (cfg, map_obj, description, rspamd_glob_list_read_single, rspamd_regexp_list_fin, + rspamd_regexp_list_dtor, (void **) &map->data.re_map)) == NULL) { lua_pushnil (L); ucl_object_unref (map_obj); diff --git a/src/lua/lua_mimepart.c b/src/lua/lua_mimepart.c index 080bf4662..6ed06f22d 100644 --- a/src/lua/lua_mimepart.c +++ b/src/lua/lua_mimepart.c @@ -18,6 +18,8 @@ #include "libmime/message.h" #include "libmime/lang_detection.h" #include "libstat/stat_api.h" +#include "libcryptobox/cryptobox.h" +#include "libutil/shingles.h" /* Textpart methods */ /*** @@ -169,6 +171,17 @@ LUA_FUNCTION_DEF (textpart, get_language); */ LUA_FUNCTION_DEF (textpart, get_languages); /*** + * @method text_part:get_fuzzy_hashes(mempool) + * @param {rspamd_mempool} mempool - memory pool (usually task pool) + * Returns direct hash + array of shingles being calculated as following: + * - [1] - fuzzy digest as a string + * - [2..33] - fuzzy hashes as the following tables: + * - [1] - 64 bit integer represented as a string + * - [2..4] - strings used to generate this hash + * @return {string,array|tables} fuzzy hashes calculated + */ +LUA_FUNCTION_DEF (textpart, get_fuzzy_hashes); +/*** * @method text_part:get_mimepart() * Returns the mime part object corresponding to this text part * @return {mimepart} mimepart object @@ -195,6 +208,7 @@ static const struct luaL_reg textpartlib_m[] = { LUA_INTERFACE_DEF (textpart, get_languages), LUA_INTERFACE_DEF (textpart, get_mimepart), LUA_INTERFACE_DEF (textpart, get_stats), + LUA_INTERFACE_DEF (textpart, get_fuzzy_hashes), {"__tostring", rspamd_lua_class_tostring}, {NULL, NULL} }; @@ -263,6 +277,15 @@ end */ LUA_FUNCTION_DEF (mimepart, get_header_full); /*** + * @method mimepart:get_header_count(name[, case_sensitive]) + * Lightweight version if you need just a header's count + * * By default headers are searched in caseless matter. + * @param {string} name name of header to get + * @param {boolean} case_sensitive case sensitiveness flag to search for a header + * @return {number} number of header's occurrencies or 0 if not found + */ +LUA_FUNCTION_DEF (mimepart, get_header_count); +/*** * @method mime_part:get_content() * Get the parsed content of part * @return {text} opaque text object (zero-copy if not casted to lua string) @@ -410,6 +433,7 @@ static const struct luaL_reg mimepartlib_m[] = { LUA_INTERFACE_DEF (mimepart, get_header), LUA_INTERFACE_DEF (mimepart, get_header_raw), LUA_INTERFACE_DEF (mimepart, get_header_full), + LUA_INTERFACE_DEF (mimepart, get_header_count), LUA_INTERFACE_DEF (mimepart, is_image), LUA_INTERFACE_DEF (mimepart, get_image), LUA_INTERFACE_DEF (mimepart, is_archive), @@ -827,6 +851,122 @@ lua_textpart_get_languages (lua_State * L) return 1; } +struct lua_shingle_data { + guint64 hash; + rspamd_ftok_t t1; + rspamd_ftok_t t2; + rspamd_ftok_t t3; +}; + +#define STORE_TOKEN(i, t) do { \ + if ((i) < part->normalized_words->len) { \ + word = &g_array_index (part->normalized_words, rspamd_stat_token_t, (i)); \ + sd->t.begin = word->begin; \ + sd->t.len = word->len; \ + } \ + }while (0) + +static guint64 +lua_shingles_filter (guint64 *input, gsize count, + gint shno, const guchar *key, gpointer ud) +{ + guint64 minimal = G_MAXUINT64; + gsize i, min_idx = 0; + struct lua_shingle_data *sd; + rspamd_stat_token_t *word; + struct rspamd_mime_text_part *part = (struct rspamd_mime_text_part *)ud; + + for (i = 0; i < count; i ++) { + if (minimal > input[i]) { + minimal = input[i]; + min_idx = i; + } + } + + sd = g_malloc0 (sizeof (*sd)); + sd->hash = minimal; + + + STORE_TOKEN (min_idx, t1); + STORE_TOKEN (min_idx + 1, t2); + STORE_TOKEN (min_idx + 2, t3); + + return GPOINTER_TO_SIZE (sd); +} + +#undef STORE_TOKEN + +static gint +lua_textpart_get_fuzzy_hashes (lua_State * L) +{ + struct rspamd_mime_text_part *part = lua_check_textpart (L); + rspamd_mempool_t *pool = rspamd_lua_check_mempool (L, 2); + guchar key[rspamd_cryptobox_HASHBYTES], digest[rspamd_cryptobox_HASHBYTES], + hexdigest[rspamd_cryptobox_HASHBYTES * 2 + 1], numbuf[64]; + struct rspamd_shingle *sgl; + guint i; + struct lua_shingle_data *sd; + rspamd_cryptobox_hash_state_t st; + rspamd_stat_token_t *word; + + if (part && pool) { + /* TODO: add keys and algorithms support */ + rspamd_cryptobox_hash (key, "rspamd", strlen ("rspamd"), NULL, 0); + + /* TODO: add short text support */ + + /* Calculate direct hash */ + rspamd_cryptobox_hash_init (&st, key, rspamd_cryptobox_HASHKEYBYTES); + + for (i = 0; i < part->normalized_words->len; i ++) { + word = &g_array_index (part->normalized_words, rspamd_stat_token_t, i); + rspamd_cryptobox_hash_update (&st, word->begin, word->len); + } + + rspamd_cryptobox_hash_final (&st, digest); + + rspamd_encode_hex_buf (digest, sizeof (digest), hexdigest, + sizeof (hexdigest)); + lua_pushlstring (L, hexdigest, sizeof (hexdigest) - 1); + + sgl = rspamd_shingles_from_text (part->normalized_words, key, + pool, lua_shingles_filter, part, RSPAMD_SHINGLES_MUMHASH); + + if (sgl == NULL) { + lua_pushnil (L); + } + else { + lua_createtable (L, G_N_ELEMENTS (sgl->hashes), 0); + + for (i = 0; i < G_N_ELEMENTS (sgl->hashes); i ++) { + sd = GSIZE_TO_POINTER (sgl->hashes[i]); + + lua_createtable (L, 4, 0); + rspamd_snprintf (numbuf, sizeof (numbuf), "%uL", sd->hash); + lua_pushstring (L, numbuf); + lua_rawseti (L, -2, 1); + + /* Tokens */ + lua_pushlstring (L, sd->t1.begin, sd->t1.len); + lua_rawseti (L, -2, 2); + + lua_pushlstring (L, sd->t2.begin, sd->t2.len); + lua_rawseti (L, -2, 3); + + lua_pushlstring (L, sd->t3.begin, sd->t3.len); + lua_rawseti (L, -2, 4); + + lua_rawseti (L, -2, i + 1); /* Store table */ + } + } + } + else { + return luaL_error (L, "invalid arguments"); + } + + return 2; +} + static gint lua_textpart_get_mimepart (lua_State * L) { @@ -1056,7 +1196,7 @@ lua_mimepart_get_filename (lua_State * L) } static gint -lua_mimepart_get_header_common (lua_State *L, gboolean full, gboolean raw) +lua_mimepart_get_header_common (lua_State *L, enum rspamd_lua_task_header_type how) { struct rspamd_mime_part *part = lua_check_mimepart (L); const gchar *name; @@ -1069,7 +1209,7 @@ lua_mimepart_get_header_common (lua_State *L, gboolean full, gboolean raw) ar = rspamd_message_get_header_from_hash (part->raw_headers, NULL, name, FALSE); - return rspamd_lua_push_header_array (L, ar, full, raw); + return rspamd_lua_push_header_array (L, ar, how); } lua_pushnil (L); @@ -1080,19 +1220,25 @@ lua_mimepart_get_header_common (lua_State *L, gboolean full, gboolean raw) static gint lua_mimepart_get_header_full (lua_State * L) { - return lua_mimepart_get_header_common (L, TRUE, TRUE); + return lua_mimepart_get_header_common (L, RSPAMD_TASK_HEADER_PUSH_FULL); } static gint lua_mimepart_get_header (lua_State * L) { - return lua_mimepart_get_header_common (L, FALSE, FALSE); + return lua_mimepart_get_header_common (L, RSPAMD_TASK_HEADER_PUSH_SIMPLE); } static gint lua_mimepart_get_header_raw (lua_State * L) { - return lua_mimepart_get_header_common (L, FALSE, TRUE); + return lua_mimepart_get_header_common (L, RSPAMD_TASK_HEADER_PUSH_RAW); +} + +static gint +lua_mimepart_get_header_count (lua_State * L) +{ + return lua_mimepart_get_header_common (L, RSPAMD_TASK_HEADER_PUSH_COUNT); } static gint @@ -1288,7 +1434,7 @@ static gint lua_mimepart_headers_foreach (lua_State *L) { struct rspamd_mime_part *part = lua_check_mimepart (L); - gboolean full = FALSE, raw = FALSE; + enum rspamd_lua_task_header_type how = RSPAMD_TASK_HEADER_PUSH_SIMPLE; struct rspamd_lua_regexp *re = NULL; GList *cur; struct rspamd_mime_header *hdr; @@ -1299,8 +1445,8 @@ lua_mimepart_headers_foreach (lua_State *L) lua_pushstring (L, "full"); lua_gettable (L, 3); - if (lua_isboolean (L, -1)) { - full = lua_toboolean (L, -1); + if (lua_isboolean (L, -1) && lua_toboolean (L, -1)) { + how = RSPAMD_TASK_HEADER_PUSH_FULL; } lua_pop (L, 1); @@ -1308,8 +1454,8 @@ lua_mimepart_headers_foreach (lua_State *L) lua_pushstring (L, "raw"); lua_gettable (L, 3); - if (lua_isboolean (L, -1)) { - raw = lua_toboolean (L, -1); + if (lua_isboolean (L, -1) && lua_toboolean (L, -1)) { + how = RSPAMD_TASK_HEADER_PUSH_RAW; } lua_pop (L, 1); @@ -1342,7 +1488,7 @@ lua_mimepart_headers_foreach (lua_State *L) old_top = lua_gettop (L); lua_pushvalue (L, 2); lua_pushstring (L, hdr->name); - rspamd_lua_push_header (L, hdr, full, raw); + rspamd_lua_push_header (L, hdr, how); if (lua_pcall (L, 2, LUA_MULTRET, 0) != 0) { msg_err ("call to header_foreach failed: %s", diff --git a/src/lua/lua_task.c b/src/lua/lua_task.c index 1807888c8..83adc99a2 100644 --- a/src/lua/lua_task.c +++ b/src/lua/lua_task.c @@ -21,7 +21,10 @@ #include "unix-std.h" #include "libmime/smtp_parsers.h" #include "libserver/mempool_vars_internal.h" +#include "libserver/task.h" +#include "libstat/stat_api.h" #include <math.h> +#include <src/libserver/task.h> /*** * @module rspamd_task @@ -44,7 +47,24 @@ end */ /* Task methods */ +/*** + * @function rspamd_task.load_from_file(filename[, cfg]) + * Loads a message from specific file + * @return {boolean},{rspamd_task|error} status + new task or error message + */ +LUA_FUNCTION_DEF (task, load_from_file); +/*** + * @function rspamd_task.load_from_string(message[, cfg]) + * Loads a message from specific file + * @return {boolean},{rspamd_task|error} status + new task or error message + */ +LUA_FUNCTION_DEF (task, load_from_string); + LUA_FUNCTION_DEF (task, get_message); +/*** + * @method task:process_message() + * Parses message + */ LUA_FUNCTION_DEF (task, process_message); /*** * @method task:get_cfg() @@ -131,6 +151,14 @@ local function cb(task) end */ LUA_FUNCTION_DEF (task, set_pre_result); + +/*** + * @method task:has_pre_result() + * Returns true if task has some pre-result being set + * + * @return {boolean} true if task has some pre-result being set + */ +LUA_FUNCTION_DEF (task, has_pre_result); /*** * @method task:append_message(message) * Adds a message to scanning output. @@ -269,7 +297,15 @@ function check_header_delimiter_tab(task, header_name) end */ LUA_FUNCTION_DEF (task, get_header_full); - +/*** + * @method task:get_header_count(name[, case_sensitive]) + * Lightweight version if you need just a header's count + * * By default headers are searched in caseless matter. + * @param {string} name name of header to get + * @param {boolean} case_sensitive case sensitiveness flag to search for a header + * @return {number} number of header's occurrencies or 0 if not found + */ +LUA_FUNCTION_DEF (task, get_header_count); /*** * @method task:get_raw_headers() * Get all undecoded headers of a message as a string @@ -431,6 +467,11 @@ LUA_FUNCTION_DEF (task, set_from); * @return {string} username or nil */ LUA_FUNCTION_DEF (task, get_user); +/*** + * @method task:set_user([username]) + * Sets or resets (if username is not specified) authenticated user name for this task. + * @return {string} the previously set username or nil + */ LUA_FUNCTION_DEF (task, set_user); /*** * @method task:get_from_ip() @@ -852,7 +893,28 @@ LUA_FUNCTION_DEF (task, disable_action); */ LUA_FUNCTION_DEF (task, get_newlines_type); +/*** + * @method task:get_stat_tokens() + * Returns list of tables the statistical tokens: + * - `data`: 64 bit number encoded as a string + * - `t1`: the first token (if any) + * - `t2`: the second token (if any) + * - `win`: window index + * - `flag`: table of strings: + * - `text`: text token + * - `meta`: meta token + * - `lua`: lua meta token + * - `exception`: exception + * - `subject`: subject token + * - `unigram`: unigram token + * + * @return {table of tables} + */ +LUA_FUNCTION_DEF (task, get_stat_tokens); + static const struct luaL_reg tasklib_f[] = { + LUA_INTERFACE_DEF (task, load_from_file), + LUA_INTERFACE_DEF (task, load_from_string), {NULL, NULL} }; @@ -869,6 +931,7 @@ static const struct luaL_reg tasklib_m[] = { LUA_INTERFACE_DEF (task, insert_result), LUA_INTERFACE_DEF (task, adjust_result), LUA_INTERFACE_DEF (task, set_pre_result), + LUA_INTERFACE_DEF (task, has_pre_result), LUA_INTERFACE_DEF (task, append_message), LUA_INTERFACE_DEF (task, has_urls), LUA_INTERFACE_DEF (task, get_urls), @@ -883,6 +946,7 @@ static const struct luaL_reg tasklib_m[] = { LUA_INTERFACE_DEF (task, get_header), LUA_INTERFACE_DEF (task, get_header_raw), LUA_INTERFACE_DEF (task, get_header_full), + LUA_INTERFACE_DEF (task, get_header_count), LUA_INTERFACE_DEF (task, get_raw_headers), LUA_INTERFACE_DEF (task, get_received_headers), LUA_INTERFACE_DEF (task, get_queue_id), @@ -946,6 +1010,7 @@ static const struct luaL_reg tasklib_m[] = { LUA_INTERFACE_DEF (task, headers_foreach), LUA_INTERFACE_DEF (task, disable_action), LUA_INTERFACE_DEF (task, get_newlines_type), + LUA_INTERFACE_DEF (task, get_stat_tokens), {"__tostring", rspamd_lua_class_tostring}, {NULL, NULL} }; @@ -1000,6 +1065,7 @@ static const struct luaL_reg textlib_m[] = { LUA_INTERFACE_DEF (text, ptr), LUA_INTERFACE_DEF (text, take_ownership), LUA_INTERFACE_DEF (text, save_in_file), + {"write", lua_text_save_in_file}, {"__len", lua_text_len}, {"__tostring", lua_text_str}, {"__gc", lua_text_gc}, @@ -1168,6 +1234,123 @@ lua_task_get_message (lua_State * L) return luaL_error (L, "task:get_message is no longer supported"); } +static void +lua_task_unmap_dtor (gpointer p) +{ + struct rspamd_task *task = (struct rspamd_task *)p; + + if (task->msg.begin) { + munmap ((gpointer)task->msg.begin, task->msg.len); + } +} + +static void +lua_task_free_dtor (gpointer p) +{ + struct rspamd_task *task = (struct rspamd_task *)p; + + if (task->msg.begin) { + g_free ((gpointer)task->msg.begin); + } +} + +static gint +lua_task_load_from_file (lua_State * L) +{ + struct rspamd_task *task = NULL, **ptask; + const gchar *fname = luaL_checkstring (L, 1), *err = NULL; + struct rspamd_config *cfg = NULL; + gboolean res = FALSE; + gpointer map; + gsize sz; + + if (fname) { + + if (lua_type (L, 2) == LUA_TUSERDATA) { + gpointer p; + p = rspamd_lua_check_udata_maybe (L, 2, "rspamd{config}"); + + if (p) { + cfg = *(struct rspamd_config **)p; + } + } + + map = rspamd_file_xmap (fname, PROT_READ, &sz, TRUE); + + if (!map) { + err = strerror (errno); + } + else { + task = rspamd_task_new (NULL, cfg, NULL, NULL); + task->msg.begin = map; + task->msg.len = sz; + rspamd_mempool_add_destructor (task->task_pool, + lua_task_unmap_dtor, task); + res = TRUE; + } + } + else { + return luaL_error (L, "invalid arguments"); + } + + lua_pushboolean (L, res); + + if (res) { + ptask = lua_newuserdata (L, sizeof (*ptask)); + *ptask = task; + rspamd_lua_setclass (L, "rspamd{task}", -1); + } + else { + if (err) { + lua_pushstring (L, err); + } + else { + lua_pushnil (L); + } + } + + return 2; +} + +static gint +lua_task_load_from_string (lua_State * L) +{ + struct rspamd_task *task = NULL, **ptask; + const gchar *str_message; + gsize message_len; + struct rspamd_config *cfg = NULL; + + str_message = luaL_checklstring (L, 1, &message_len); + + if (str_message) { + + if (lua_type (L, 2) == LUA_TUSERDATA) { + gpointer p; + p = rspamd_lua_check_udata_maybe (L, 2, "rspamd{config}"); + + if (p) { + cfg = *(struct rspamd_config **)p; + } + } + + task = rspamd_task_new (NULL, cfg, NULL, NULL); + task->msg.begin = g_strdup (str_message); + task->msg.len = message_len; + rspamd_mempool_add_destructor (task->task_pool, lua_task_free_dtor, task); + } + else { + return luaL_error (L, "invalid arguments"); + } + + lua_pushboolean (L, true); + + ptask = lua_newuserdata (L, sizeof (*ptask)); + *ptask = task; + rspamd_lua_setclass (L, "rspamd{task}", -1); + + return 2; +} + static int lua_task_get_mempool (lua_State * L) { @@ -1319,7 +1502,7 @@ lua_task_adjust_result (lua_State * L) metric_res = task->result; if (metric_res) { - s = g_hash_table_lookup (metric_res->symbols, symbol_name); + s = rspamd_task_find_symbol_result (task, symbol_name); } else { return luaL_error (L, "no metric result"); @@ -1392,7 +1575,7 @@ lua_task_set_pre_result (lua_State * L) action_str = rspamd_mempool_strdup (task->task_pool, luaL_checkstring (L, 3)); task->pre_result.str = action_str; - ucl_object_insert_key (task->messages, + ucl_object_replace_key (task->messages, ucl_object_fromstring (action_str), "smtp_message", 0, false); } @@ -1422,6 +1605,21 @@ lua_task_set_pre_result (lua_State * L) } static gint +lua_task_has_pre_result (lua_State * L) +{ + struct rspamd_task *task = lua_check_task (L, 1); + + if (task) { + lua_pushboolean (L, task->pre_result.action != METRIC_ACTION_MAX); + } + else { + return luaL_error (L, "invalid arguments"); + } + + return 1; +} + +static gint lua_task_append_message (lua_State * L) { struct rspamd_task *task = lua_check_task (L, 1); @@ -1763,13 +1961,15 @@ lua_task_set_request_header (lua_State *L) return 0; } + + gint rspamd_lua_push_header (lua_State *L, struct rspamd_mime_header *rh, - gboolean full, gboolean raw) + enum rspamd_lua_task_header_type how) { - const gchar *val; - if (full) { + switch (how) { + case RSPAMD_TASK_HEADER_PUSH_FULL: /* Create new associated table for a header */ lua_createtable (L, 0, 7); rspamd_lua_table_set (L, "name", rh->name); @@ -1792,21 +1992,27 @@ rspamd_lua_push_header (lua_State *L, struct rspamd_mime_header *rh, lua_pushstring (L, "order"); lua_pushnumber (L, rh->order); lua_settable (L, -3); - } - else { - if (!raw) { - val = rh->decoded; + break; + case RSPAMD_TASK_HEADER_PUSH_RAW: + if (rh->value) { + lua_pushstring (L, rh->value); } else { - val = rh->value; + lua_pushnil (L); } - - if (val) { - lua_pushstring (L, val); + break; + case RSPAMD_TASK_HEADER_PUSH_SIMPLE: + if (rh->decoded) { + lua_pushstring (L, rh->decoded); } else { lua_pushnil (L); } + break; + case RSPAMD_TASK_HEADER_PUSH_COUNT: + default: + g_assert_not_reached (); + break; } return 1; @@ -1814,39 +2020,45 @@ rspamd_lua_push_header (lua_State *L, struct rspamd_mime_header *rh, gint rspamd_lua_push_header_array (lua_State * L, - GPtrArray *ar, - gboolean full, - gboolean raw) + GPtrArray *ar, + enum rspamd_lua_task_header_type how) { struct rspamd_mime_header *rh; guint i; - if (ar == NULL || ar->len == 0) { - lua_pushnil (L); + if (how == RSPAMD_TASK_HEADER_PUSH_COUNT) { + lua_pushnumber (L, 0); + } + else { + lua_pushnil (L); + } + return 1; } - if (full) { + if (how == RSPAMD_TASK_HEADER_PUSH_FULL) { lua_createtable (L, ar->len, 0); - } - - PTR_ARRAY_FOREACH (ar, i, rh) { - if (full) { - rspamd_lua_push_header (L, rh, full, raw); + PTR_ARRAY_FOREACH (ar, i, rh) { + rspamd_lua_push_header (L, rh, how); lua_rawseti (L, -2, i + 1); } - else { - return rspamd_lua_push_header (L, rh, full, raw); - } + } + else if (how == RSPAMD_TASK_HEADER_PUSH_COUNT) { + lua_pushnumber (L, ar->len); + } + else { + rh = g_ptr_array_index (ar, 0); + + return rspamd_lua_push_header (L, rh, how); } return 1; } static gint -lua_task_get_header_common (lua_State *L, gboolean full, gboolean raw) +lua_task_get_header_common (lua_State *L, enum rspamd_lua_task_header_type how) { gboolean strong = FALSE; struct rspamd_task *task = lua_check_task (L, 1); @@ -1862,7 +2074,7 @@ lua_task_get_header_common (lua_State *L, gboolean full, gboolean raw) ar = rspamd_message_get_header_array (task, name, strong); - return rspamd_lua_push_header_array (L, ar, full, raw); + return rspamd_lua_push_header_array (L, ar, how); } else { return luaL_error (L, "invalid arguments"); @@ -1872,19 +2084,25 @@ lua_task_get_header_common (lua_State *L, gboolean full, gboolean raw) static gint lua_task_get_header_full (lua_State * L) { - return lua_task_get_header_common (L, TRUE, TRUE); + return lua_task_get_header_common (L, RSPAMD_TASK_HEADER_PUSH_FULL); } static gint lua_task_get_header (lua_State * L) { - return lua_task_get_header_common (L, FALSE, FALSE); + return lua_task_get_header_common (L, RSPAMD_TASK_HEADER_PUSH_SIMPLE); } static gint lua_task_get_header_raw (lua_State * L) { - return lua_task_get_header_common (L, FALSE, TRUE); + return lua_task_get_header_common (L, RSPAMD_TASK_HEADER_PUSH_RAW); +} + +static gint +lua_task_get_header_count (lua_State * L) +{ + return lua_task_get_header_common (L, RSPAMD_TASK_HEADER_PUSH_COUNT); } static gint @@ -2782,16 +3000,38 @@ lua_task_set_user (lua_State *L) const gchar *new_user; if (task) { - new_user = luaL_checkstring (L, 2); - if (new_user) { + + if (lua_type (L, 2) == LUA_TSTRING) { + new_user = lua_tostring (L, 2); + + if (task->user) { + /* Push old user */ + lua_pushstring (L, task->user); + } + else { + lua_pushnil (L); + } + task->user = rspamd_mempool_strdup (task->task_pool, new_user); } + else { + /* Reset user */ + if (task->user) { + /* Push old user */ + lua_pushstring (L, task->user); + } + else { + lua_pushnil (L); + } + + task->user = NULL; + } } else { return luaL_error (L, "invalid arguments"); } - return 0; + return 1; } static gint @@ -3062,7 +3302,7 @@ lua_push_symbol_result (lua_State *L, metric_res = task->result; if (metric_res) { - s = g_hash_table_lookup (metric_res->symbols, symbol); + s = rspamd_task_find_symbol_result (task, symbol); } } else { @@ -3111,7 +3351,7 @@ lua_push_symbol_result (lua_State *L, if (s->options) { lua_pushstring (L, "options"); - lua_createtable (L, g_hash_table_size (s->options), 0); + lua_createtable (L, kh_size (s->options), 0); DL_FOREACH (s->opts_head, opt) { lua_pushstring (L, (const char*)opt->option); @@ -3165,18 +3405,12 @@ lua_task_has_symbol (lua_State *L) { struct rspamd_task *task = lua_check_task (L, 1); const gchar *symbol; - struct rspamd_metric_result *mres; gboolean found = FALSE; symbol = luaL_checkstring (L, 2); if (task && symbol) { - mres = task->result; - - if (mres) { - found = g_hash_table_lookup (mres->symbols, symbol) != NULL; - } - + found = (rspamd_task_find_symbol_result (task, symbol) != NULL); lua_pushboolean (L, found); } else { @@ -3192,26 +3426,24 @@ lua_task_get_symbols (lua_State *L) struct rspamd_task *task = lua_check_task (L, 1); struct rspamd_metric_result *mres; gint i = 1; - GHashTableIter it; - gpointer k, v; struct rspamd_symbol_result *s; if (task) { mres = task->result; if (mres) { - lua_createtable (L, g_hash_table_size (mres->symbols), 0); - lua_createtable (L, g_hash_table_size (mres->symbols), 0); - g_hash_table_iter_init (&it, mres->symbols); - - while (g_hash_table_iter_next (&it, &k, &v)) { - s = v; - lua_pushstring (L, k); - lua_rawseti (L, -3, i); - lua_pushnumber (L, s->score); - lua_rawseti (L, -2, i); - i ++; - } + lua_createtable (L, kh_size (mres->symbols), 0); + lua_createtable (L, kh_size (mres->symbols), 0); + + kh_foreach_value_ptr (mres->symbols, s, { + if (!(s->flags & RSPAMD_SYMBOL_RESULT_IGNORED)) { + lua_pushstring (L, s->name); + lua_rawseti (L, -3, i); + lua_pushnumber (L, s->score); + lua_rawseti (L, -2, i); + i++; + } + }); } else { lua_createtable (L, 0, 0); @@ -3230,8 +3462,7 @@ lua_task_get_symbols_all (lua_State *L) { struct rspamd_task *task = lua_check_task (L, 1); struct rspamd_metric_result *mres; - GHashTableIter it; - gpointer k, v; + struct rspamd_symbol_result *s; gboolean found = FALSE; gint i = 1; @@ -3240,13 +3471,12 @@ lua_task_get_symbols_all (lua_State *L) if (mres) { found = TRUE; - lua_createtable (L, g_hash_table_size (mres->symbols), 0); - g_hash_table_iter_init (&it, mres->symbols); + lua_createtable (L, kh_size (mres->symbols), 0); - while (g_hash_table_iter_next (&it, &k, &v)) { - lua_push_symbol_result (L, task, k, v, FALSE, TRUE); + kh_foreach_value_ptr (mres->symbols, s, { + lua_push_symbol_result (L, task, s->name, s, FALSE, TRUE); lua_rawseti (L, -2, i++); - } + }); } } else { @@ -3267,29 +3497,28 @@ lua_task_get_symbols_numeric (lua_State *L) struct rspamd_task *task = lua_check_task (L, 1); struct rspamd_metric_result *mres; gint i = 1, id; - GHashTableIter it; - gpointer k, v; struct rspamd_symbol_result *s; if (task) { mres = task->result; if (mres) { - lua_createtable (L, g_hash_table_size (mres->symbols), 0); - lua_createtable (L, g_hash_table_size (mres->symbols), 0); - - g_hash_table_iter_init (&it, mres->symbols); - - while (g_hash_table_iter_next (&it, &k, &v)) { - id = rspamd_symbols_cache_find_symbol (task->cfg->cache, - k); - s = v; - lua_pushnumber (L, id); - lua_rawseti (L, -3, i); - lua_pushnumber (L, s->score); - lua_rawseti (L, -2, i); - i ++; - } + lua_createtable (L, kh_size (mres->symbols), 0); + lua_createtable (L, kh_size (mres->symbols), 0); + + lua_createtable (L, kh_size (mres->symbols), 0); + + kh_foreach_value_ptr (mres->symbols, s, { + if (!(s->flags & RSPAMD_SYMBOL_RESULT_IGNORED)) { + id = rspamd_symbols_cache_find_symbol (task->cfg->cache, + s->name); + lua_pushnumber (L, id); + lua_rawseti (L, -3, i); + lua_pushnumber (L, s->score); + lua_rawseti (L, -2, i); + i++; + } + }); } else { lua_createtable (L, 0, 0); @@ -3313,7 +3542,6 @@ struct tokens_foreach_cbdata { static void tokens_foreach_cb (gint id, const gchar *sym, gint flags, gpointer ud) { - struct rspamd_metric_result *mres; struct tokens_foreach_cbdata *cbd = ud; struct rspamd_symbol_result *s; @@ -3321,9 +3549,7 @@ tokens_foreach_cb (gint id, const gchar *sym, gint flags, gpointer ud) return; } - mres = cbd->task->result; - - if (mres && (s = g_hash_table_lookup (mres->symbols, sym)) != NULL) { + if ((s = rspamd_task_find_symbol_result (cbd->task, sym)) != NULL) { if (cbd->normalize) { lua_pushnumber (cbd->L, tanh (s->score)); } @@ -4315,6 +4541,112 @@ lua_task_get_newlines_type (lua_State *L) return 1; } +static void +lua_push_stat_token (lua_State *L, rspamd_token_t *tok) +{ + gchar numbuf[64]; + + /* Table values + * - `data`: 64 bit number encoded as a string + * - `t1`: the first token (if any) + * - `t2`: the second token (if any) + * - `win`: window index + * - `flag`: table of strings: + * - `text`: text token + * - `meta`: meta token + * - `lua`: lua meta token + * - `exception`: exception + * - `subject`: subject token + * - `unigram`: unigram token + */ + lua_createtable (L, 0, 5); + + rspamd_snprintf (numbuf, sizeof (numbuf), "%uL", tok->data); + lua_pushstring (L, "data"); + lua_pushstring (L, numbuf); + lua_settable (L, -3); + + if (tok->t1) { + lua_pushstring (L, "t1"); + lua_pushlstring (L, tok->t1->begin, tok->t1->len); + lua_settable (L, -3); + } + + if (tok->t2) { + lua_pushstring (L, "t2"); + lua_pushlstring (L, tok->t2->begin, tok->t2->len); + lua_settable (L, -3); + } + + lua_pushstring (L, "win"); + lua_pushnumber (L, tok->window_idx); + lua_settable (L, -3); + + lua_pushstring (L, "flags"); + lua_createtable (L, 0, 5); + + /* Flags */ + { + if (tok->flags & RSPAMD_STAT_TOKEN_FLAG_TEXT) { + lua_pushstring (L, "text"); + lua_pushboolean (L, true); + lua_settable (L, -3); + } + if (tok->flags & RSPAMD_STAT_TOKEN_FLAG_META) { + lua_pushstring (L, "meta"); + lua_pushboolean (L, true); + lua_settable (L, -3); + } + if (tok->flags & RSPAMD_STAT_TOKEN_FLAG_LUA_META) { + lua_pushstring (L, "lua"); + lua_pushboolean (L, true); + lua_settable (L, -3); + } + if (tok->flags & RSPAMD_STAT_TOKEN_FLAG_EXCEPTION) { + lua_pushstring (L, "exception"); + lua_pushboolean (L, true); + lua_settable (L, -3); + } + if (tok->flags & RSPAMD_STAT_TOKEN_FLAG_SUBJECT) { + lua_pushstring (L, "subject"); + lua_pushboolean (L, true); + lua_settable (L, -3); + } + } + lua_settable (L, -3); +} + +static gint +lua_task_get_stat_tokens (lua_State *L) +{ + struct rspamd_task *task = lua_check_task (L, 1); + guint i; + rspamd_token_t *tok; + + if (task) { + if (!task->tokens) { + rspamd_stat_process_tokenize (NULL, task); + } + + if (!task->tokens) { + lua_pushnil (L); + } + else { + lua_createtable (L, task->tokens->len, 0); + + PTR_ARRAY_FOREACH (task->tokens, i, tok) { + lua_push_stat_token (L, tok); + lua_rawseti (L, -2, i + 1); + } + } + } + else { + return luaL_error (L, "invalid arguments"); + } + + return 1; +} + static gint lua_task_set_metric_subject (lua_State *L) { @@ -4402,7 +4734,7 @@ static gint lua_task_headers_foreach (lua_State *L) { struct rspamd_task *task = lua_check_task (L, 1); - gboolean full = FALSE, raw = FALSE; + enum rspamd_lua_task_header_type how = RSPAMD_TASK_HEADER_PUSH_SIMPLE; struct rspamd_lua_regexp *re = NULL; GList *cur; struct rspamd_mime_header *hdr; @@ -4413,8 +4745,8 @@ lua_task_headers_foreach (lua_State *L) lua_pushstring (L, "full"); lua_gettable (L, 3); - if (lua_isboolean (L, -1)) { - full = lua_toboolean (L, -1); + if (lua_isboolean (L, -1) && lua_toboolean (L, -1)) { + how = RSPAMD_TASK_HEADER_PUSH_FULL; } lua_pop (L, 1); @@ -4422,8 +4754,8 @@ lua_task_headers_foreach (lua_State *L) lua_pushstring (L, "raw"); lua_gettable (L, 3); - if (lua_isboolean (L, -1)) { - raw = lua_toboolean (L, -1); + if (lua_isboolean (L, -1) && lua_toboolean (L, -1)) { + how = RSPAMD_TASK_HEADER_PUSH_RAW; } lua_pop (L, 1); @@ -4456,7 +4788,7 @@ lua_task_headers_foreach (lua_State *L) old_top = lua_gettop (L); lua_pushvalue (L, 2); lua_pushstring (L, hdr->name); - rspamd_lua_push_header (L, hdr, full, raw); + rspamd_lua_push_header (L, hdr, how); if (lua_pcall (L, 2, LUA_MULTRET, 0) != 0) { msg_err ("call to header_foreach failed: %s", @@ -4765,33 +5097,47 @@ static gint lua_text_save_in_file (lua_State *L) { struct rspamd_lua_text *t = lua_check_text (L, 1); - const gchar *fname = luaL_checkstring (L, 2); + const gchar *fname = NULL; guint mode = 00644; gint fd; - if (t != NULL && fname != NULL) { + if (t != NULL) { + if (lua_type (L, 2) == LUA_TSTRING) { + fname = luaL_checkstring (L, 2); + } if (lua_type (L, 3) == LUA_TNUMBER) { mode = lua_tonumber (L, 3); } - fd = rspamd_file_xopen (fname, O_CREAT | O_WRONLY | O_EXCL, mode, 0); + if (fname) { + fd = rspamd_file_xopen (fname, O_CREAT | O_WRONLY | O_EXCL, mode, 0); - if (fd == -1) { - lua_pushboolean (L, false); - lua_pushstring (L, strerror (errno)); + if (fd == -1) { + lua_pushboolean (L, false); + lua_pushstring (L, strerror (errno)); - return 2; + return 2; + } + } + else { + fd = STDOUT_FILENO; } if (write (fd, t->start, t->len) == -1) { - close (fd); + if (fd != STDOUT_FILENO) { + close (fd); + } + lua_pushboolean (L, false); lua_pushstring (L, strerror (errno)); return 2; } - close (fd); + if (fd != STDOUT_FILENO) { + close (fd); + } + lua_pushboolean (L, true); } else { diff --git a/src/lua/lua_upstream.c b/src/lua/lua_upstream.c index e403d34af..0d1246229 100644 --- a/src/lua/lua_upstream.c +++ b/src/lua/lua_upstream.c @@ -124,9 +124,15 @@ static gint lua_upstream_fail (lua_State *L) { struct upstream *up = lua_check_upstream (L); + gboolean fail_addr = FALSE; if (up) { - rspamd_upstream_fail (up); + + if (lua_isboolean (L, 2)) { + fail_addr = lua_toboolean (L, 2); + } + + rspamd_upstream_fail (up, fail_addr); } return 0; diff --git a/src/lua/lua_util.c b/src/lua/lua_util.c index f99ae7d1f..af1c13ad2 100644 --- a/src/lua/lua_util.c +++ b/src/lua/lua_util.c @@ -45,7 +45,7 @@ LUA_FUNCTION_DEF (util, create_event_base); */ LUA_FUNCTION_DEF (util, load_rspamd_config); /*** - * @function util.config_from_ucl(any) + * @function util.config_from_ucl(any, string) * Load rspamd config from ucl reperesented by any lua table * @return {confg} new configuration object suitable for access */ @@ -414,7 +414,7 @@ LUA_FUNCTION_DEF (util, readpassphrase); /*** * @function util.file_exists(file) * Checks if a specified file exists and is available for reading - * @return {boolean} true if file exists + * @return {boolean,string} true if file exists + string error if not */ LUA_FUNCTION_DEF (util, file_exists); @@ -666,17 +666,67 @@ lua_util_load_rspamd_config (lua_State *L) } static gint +parse_config_options (const char *str_options) +{ + gint ret = 0; + gchar **vec; + const gchar *str; + guint i, l; + + vec = g_strsplit_set (str_options, ",;", -1); + if (vec) { + l = g_strv_length (vec); + for (i = 0; i < l; i ++) { + str = vec[i]; + + if (g_ascii_strcasecmp (str, "INIT_URL") == 0) { + ret |= RSPAMD_CONFIG_INIT_URL; + } else if (g_ascii_strcasecmp (str, "INIT_LIBS") == 0) { + ret |= RSPAMD_CONFIG_INIT_LIBS; + } else if (g_ascii_strcasecmp (str, "INIT_SYMCACHE") == 0) { + ret |= RSPAMD_CONFIG_INIT_SYMCACHE; + } else if (g_ascii_strcasecmp (str, "INIT_VALIDATE") == 0) { + ret |= RSPAMD_CONFIG_INIT_VALIDATE; + } else if (g_ascii_strcasecmp (str, "INIT_NO_TLD") == 0) { + ret |= RSPAMD_CONFIG_INIT_NO_TLD; + } else if (g_ascii_strcasecmp (str, "INIT_PRELOAD_MAPS") == 0) { + ret |= RSPAMD_CONFIG_INIT_PRELOAD_MAPS; + } else { + msg_warn ("bad type: %s", str); + } + } + + g_strfreev (vec); + } + + return ret; +} + +static gint lua_util_config_from_ucl (lua_State *L) { - struct rspamd_config *cfg, **pcfg; + struct rspamd_config *cfg = NULL, **pcfg; struct rspamd_rcl_section *top; GError *err = NULL; ucl_object_t *obj; + const char *str_options = NULL; + gint int_options = 0; + obj = ucl_object_lua_import (L, 1); + if (lua_gettop (L) == 2) { + if (lua_type (L, 2) == LUA_TSTRING) { + str_options = lua_tostring (L, 2); + int_options = parse_config_options(str_options); + } + else { + msg_err_config ("config_from_ucl: second parameter is expected to be string"); + ucl_object_unref (obj); + lua_pushnil (L); + } + } if (obj) { - cfg = g_malloc0 (sizeof (struct rspamd_config)); cfg = rspamd_config_new (RSPAMD_CONFIG_INIT_SKIP_LUA); cfg->lua_state = L; @@ -690,7 +740,7 @@ lua_util_config_from_ucl (lua_State *L) lua_pushnil (L); } else { - rspamd_config_post_load (cfg, 0); + rspamd_config_post_load (cfg, int_options); pcfg = lua_newuserdata (L, sizeof (struct rspamd_config *)); rspamd_lua_setclass (L, "rspamd{config}", -1); *pcfg = cfg; @@ -2399,15 +2449,24 @@ static gint lua_util_file_exists (lua_State *L) { const gchar *fname = luaL_checkstring (L, 1); + gint serrno; if (fname) { - lua_pushboolean (L, access (fname, R_OK) != -1); + if (access (fname, R_OK) == -1) { + serrno = errno; + lua_pushboolean (L, false); + lua_pushstring (L, strerror (serrno)); + } + else { + lua_pushboolean (L, true); + lua_pushnil (L); + } } else { return luaL_error (L, "invalid arguments"); } - return 1; + return 2; } static gint diff --git a/src/plugins/chartable.c b/src/plugins/chartable.c index 0d409480a..9331e42dd 100644 --- a/src/plugins/chartable.c +++ b/src/plugins/chartable.c @@ -61,12 +61,13 @@ gint chartable_module_config (struct rspamd_config *cfg); gint chartable_module_reconfig (struct rspamd_config *cfg); module_t chartable_module = { - "chartable", - chartable_module_init, - chartable_module_config, - chartable_module_reconfig, - NULL, - RSPAMD_MODULE_VER + "chartable", + chartable_module_init, + chartable_module_config, + chartable_module_reconfig, + NULL, + RSPAMD_MODULE_VER, + (guint)-1, }; struct chartable_ctx { @@ -75,23 +76,26 @@ struct chartable_ctx { const gchar *url_symbol; double threshold; guint max_word_len; - - rspamd_mempool_t *chartable_pool; }; -static struct chartable_ctx *chartable_module_ctx = NULL; +static inline struct chartable_ctx * +chartable_get_context (struct rspamd_config *cfg) +{ + return (struct chartable_ctx *)g_ptr_array_index (cfg->c_modules, + chartable_module.ctx_offset); +} + static void chartable_symbol_callback (struct rspamd_task *task, void *unused); static void chartable_url_symbol_callback (struct rspamd_task *task, void *unused); gint chartable_module_init (struct rspamd_config *cfg, struct module_ctx **ctx) { - if (chartable_module_ctx == NULL) { - chartable_module_ctx = g_malloc (sizeof (struct chartable_ctx)); + struct chartable_ctx *chartable_module_ctx; - chartable_module_ctx->chartable_pool = rspamd_mempool_new (rspamd_mempool_suggest_size (), NULL); - chartable_module_ctx->max_word_len = 10; - } + chartable_module_ctx = rspamd_mempool_alloc0 (cfg->cfg_pool, + sizeof (*chartable_module_ctx)); + chartable_module_ctx->max_word_len = 10; *ctx = (struct module_ctx *)chartable_module_ctx; @@ -104,6 +108,7 @@ chartable_module_config (struct rspamd_config *cfg) { const ucl_object_t *value; gint res = TRUE; + struct chartable_ctx *chartable_module_ctx = chartable_get_context (cfg); if (!rspamd_config_is_module_enabled (cfg, "chartable")) { return TRUE; @@ -164,9 +169,6 @@ chartable_module_config (struct rspamd_config *cfg) gint chartable_module_reconfig (struct rspamd_config *cfg) { - rspamd_mempool_delete (chartable_module_ctx->chartable_pool); - chartable_module_ctx->chartable_pool = rspamd_mempool_new (1024, NULL); - return chartable_module_config (cfg); } @@ -347,9 +349,10 @@ rspamd_can_alias_latin (gint ch) static gdouble rspamd_chartable_process_word_utf (struct rspamd_task *task, - rspamd_stat_token_t *w, - gboolean is_url, - guint *ncap) + rspamd_stat_token_t *w, + gboolean is_url, + guint *ncap, + struct chartable_ctx *chartable_module_ctx) { const gchar *p, *end; gdouble badness = 0.0; @@ -465,8 +468,9 @@ rspamd_chartable_process_word_utf (struct rspamd_task *task, static gdouble rspamd_chartable_process_word_ascii (struct rspamd_task *task, - rspamd_stat_token_t *w, - gboolean is_url) + rspamd_stat_token_t *w, + gboolean is_url, + struct chartable_ctx *chartable_module_ctx) { const guchar *p, *end; gdouble badness = 0.0; @@ -549,7 +553,8 @@ rspamd_chartable_process_word_ascii (struct rspamd_task *task, static void rspamd_chartable_process_part (struct rspamd_task *task, - struct rspamd_mime_text_part *part) + struct rspamd_mime_text_part *part, + struct chartable_ctx *chartable_module_ctx) { rspamd_stat_token_t *w; guint i, ncap = 0; @@ -567,10 +572,11 @@ rspamd_chartable_process_part (struct rspamd_task *task, if (IS_PART_UTF (part)) { cur_score += rspamd_chartable_process_word_utf (task, w, FALSE, - &ncap); + &ncap, chartable_module_ctx); } else { - cur_score += rspamd_chartable_process_word_ascii (task, w, FALSE); + cur_score += rspamd_chartable_process_word_ascii (task, w, + FALSE, chartable_module_ctx); } } } @@ -600,10 +606,11 @@ chartable_symbol_callback (struct rspamd_task *task, void *unused) { guint i; struct rspamd_mime_text_part *part; + struct chartable_ctx *chartable_module_ctx = chartable_get_context (task->cfg); for (i = 0; i < task->text_parts->len; i ++) { part = g_ptr_array_index (task->text_parts, i); - rspamd_chartable_process_part (task, part); + rspamd_chartable_process_part (task, part, chartable_module_ctx); } if (task->subject != NULL) { @@ -623,7 +630,7 @@ chartable_symbol_callback (struct rspamd_task *task, void *unused) for (i = 0; i < words->len; i++) { w = &g_array_index (words, rspamd_stat_token_t, i); cur_score += rspamd_chartable_process_word_utf (task, w, FALSE, - NULL); + NULL, chartable_module_ctx); } cur_score /= (gdouble)words->len; @@ -653,6 +660,7 @@ chartable_url_symbol_callback (struct rspamd_task *task, void *unused) gpointer k, v; rspamd_stat_token_t w; gdouble cur_score = 0.0; + struct chartable_ctx *chartable_module_ctx = chartable_get_context (task->cfg); g_hash_table_iter_init (&it, task->urls); @@ -669,10 +677,12 @@ chartable_url_symbol_callback (struct rspamd_task *task, void *unused) w.len = u->hostlen; if (g_utf8_validate (w.begin, w.len, NULL)) { - cur_score += rspamd_chartable_process_word_utf (task, &w, TRUE, NULL); + cur_score += rspamd_chartable_process_word_utf (task, &w, + TRUE, NULL, chartable_module_ctx); } else { - cur_score += rspamd_chartable_process_word_ascii (task, &w, TRUE); + cur_score += rspamd_chartable_process_word_ascii (task, &w, + TRUE, chartable_module_ctx); } } } @@ -692,10 +702,12 @@ chartable_url_symbol_callback (struct rspamd_task *task, void *unused) w.len = u->hostlen; if (g_utf8_validate (w.begin, w.len, NULL)) { - cur_score += rspamd_chartable_process_word_utf (task, &w, TRUE, NULL); + cur_score += rspamd_chartable_process_word_utf (task, &w, + TRUE, NULL, chartable_module_ctx); } else { - cur_score += rspamd_chartable_process_word_ascii (task, &w, TRUE); + cur_score += rspamd_chartable_process_word_ascii (task, &w, + TRUE, chartable_module_ctx); } } } diff --git a/src/plugins/dkim_check.c b/src/plugins/dkim_check.c index 8f63a167d..543921214 100644 --- a/src/plugins/dkim_check.c +++ b/src/plugins/dkim_check.c @@ -65,7 +65,6 @@ struct dkim_ctx { const gchar *symbol_na; const gchar *symbol_permfail; - rspamd_mempool_t *dkim_pool; struct rspamd_radix_map_helper *whitelist_ip; struct rspamd_hash_map_helper *dkim_domains; guint strict_multiplier; @@ -85,13 +84,12 @@ struct dkim_check_result { rspamd_dkim_key_t *key; struct rspamd_task *task; gint res; - gint mult_allow, mult_deny; + gdouble mult_allow; + gdouble mult_deny; struct rspamd_async_watcher *w; struct dkim_check_result *next, *prev, *first; }; -static struct dkim_ctx *dkim_module_ctx = NULL; - static void dkim_symbol_callback (struct rspamd_task *task, void *unused); static void dkim_sign_callback (struct rspamd_task *task, void *unused); @@ -105,14 +103,22 @@ gint dkim_module_config (struct rspamd_config *cfg); gint dkim_module_reconfig (struct rspamd_config *cfg); module_t dkim_module = { - "dkim", - dkim_module_init, - dkim_module_config, - dkim_module_reconfig, - NULL, - RSPAMD_MODULE_VER + "dkim", + dkim_module_init, + dkim_module_config, + dkim_module_reconfig, + NULL, + RSPAMD_MODULE_VER, + (guint)-1, }; +static inline struct dkim_ctx * +dkim_get_context (struct rspamd_config *cfg) +{ + return (struct dkim_ctx *)g_ptr_array_index (cfg->c_modules, + dkim_module.ctx_offset); +} + static void dkim_module_key_dtor (gpointer k) { @@ -124,14 +130,13 @@ dkim_module_key_dtor (gpointer k) gint dkim_module_init (struct rspamd_config *cfg, struct module_ctx **ctx) { - if (dkim_module_ctx == NULL) { - dkim_module_ctx = g_malloc0 (sizeof (struct dkim_ctx)); + struct dkim_ctx *dkim_module_ctx; - dkim_module_ctx->dkim_pool = rspamd_mempool_new (rspamd_mempool_suggest_size (), "dkim"); - dkim_module_ctx->sign_headers = default_sign_headers; - dkim_module_ctx->sign_condition_ref = -1; - dkim_module_ctx->max_sigs = DEFAULT_MAX_SIGS; - } + dkim_module_ctx = rspamd_mempool_alloc0 (cfg->cfg_pool, + sizeof (*dkim_module_ctx)); + dkim_module_ctx->sign_headers = default_sign_headers; + dkim_module_ctx->sign_condition_ref = -1; + dkim_module_ctx->max_sigs = DEFAULT_MAX_SIGS; *ctx = (struct module_ctx *)dkim_module_ctx; @@ -290,6 +295,7 @@ dkim_module_config (struct rspamd_config *cfg) gint res = TRUE, cb_id = -1; guint cache_size, sign_cache_size; gboolean got_trusted = FALSE; + struct dkim_ctx *dkim_module_ctx = dkim_get_context (cfg); /* Register global methods */ lua_getglobal (cfg->lua_state, "rspamd_plugins"); @@ -400,24 +406,20 @@ dkim_module_config (struct rspamd_config *cfg) rspamd_config_radix_from_ucl (cfg, value, "DKIM whitelist", &dkim_module_ctx->whitelist_ip, NULL); - rspamd_mempool_add_destructor (dkim_module_ctx->dkim_pool, - (rspamd_mempool_destruct_t)rspamd_map_helper_destroy_radix, - dkim_module_ctx->whitelist_ip); - } if ((value = rspamd_config_get_module_opt (cfg, "dkim", "domains")) != NULL) { if (!rspamd_map_add_from_ucl (cfg, value, - "DKIM domains", rspamd_kv_list_read, rspamd_kv_list_fin, - (void **)&dkim_module_ctx->dkim_domains)) { + "DKIM domains", + rspamd_kv_list_read, + rspamd_kv_list_fin, + rspamd_kv_list_dtor, + (void **)&dkim_module_ctx->dkim_domains)) { msg_warn_config ("cannot load dkim domains list from %s", ucl_object_tostring (value)); } else { - rspamd_mempool_add_destructor (dkim_module_ctx->dkim_pool, - (rspamd_mempool_destruct_t)rspamd_map_helper_destroy_hash, - dkim_module_ctx->dkim_domains); got_trusted = TRUE; } } @@ -425,15 +427,15 @@ dkim_module_config (struct rspamd_config *cfg) if (!got_trusted && (value = rspamd_config_get_module_opt (cfg, "dkim", "trusted_domains")) != NULL) { if (!rspamd_map_add_from_ucl (cfg, value, - "DKIM domains", rspamd_kv_list_read, rspamd_kv_list_fin, + "DKIM domains", + rspamd_kv_list_read, + rspamd_kv_list_fin, + rspamd_kv_list_dtor, (void **)&dkim_module_ctx->dkim_domains)) { msg_warn_config ("cannot load dkim domains list from %s", ucl_object_tostring (value)); } else { - rspamd_mempool_add_destructor (dkim_module_ctx->dkim_pool, - (rspamd_mempool_destruct_t)rspamd_map_helper_destroy_hash, - dkim_module_ctx->dkim_domains); got_trusted = TRUE; } } @@ -469,6 +471,13 @@ dkim_module_config (struct rspamd_config *cfg) g_free, (GDestroyNotify)rspamd_dkim_sign_key_unref); + rspamd_mempool_add_destructor (cfg->cfg_pool, + (rspamd_mempool_destruct_t)rspamd_lru_hash_destroy, + dkim_module_ctx->dkim_hash); + rspamd_mempool_add_destructor (cfg->cfg_pool, + (rspamd_mempool_destruct_t)rspamd_lru_hash_destroy, + dkim_module_ctx->dkim_sign_hash); + if (dkim_module_ctx->trusted_only && !got_trusted) { msg_err_config ( "trusted_only option is set and no trusted domains are defined; disabling dkim module completely as it is useless in this case"); @@ -510,6 +519,22 @@ dkim_module_config (struct rspamd_config *cfg) SYMBOL_TYPE_VIRTUAL|SYMBOL_TYPE_FINE, cb_id); + rspamd_symbols_cache_add_symbol (cfg->cache, + "DKIM_TRACE", + 0, + NULL, NULL, + SYMBOL_TYPE_VIRTUAL|SYMBOL_TYPE_NOSTAT, + cb_id); + rspamd_config_add_symbol (cfg, + "DKIM_TRACE", + 0.0, + "DKIM trace symbol", + "policies", + RSPAMD_SYMBOL_FLAG_IGNORE, + 1, + 1); + rspamd_config_add_symbol_group (cfg, "DKIM_TRACE", "dkim"); + msg_info_config ("init internal dkim module"); #ifndef HAVE_OPENSSL msg_warn_config ( @@ -533,7 +558,7 @@ dkim_module_config (struct rspamd_config *cfg) dkim_module_ctx->sign_condition_ref = luaL_ref (cfg->lua_state, LUA_REGISTRYINDEX); rspamd_lua_add_ref_dtor (cfg->lua_state, - dkim_module_ctx->dkim_pool, + cfg->cfg_pool, dkim_module_ctx->sign_condition_ref); rspamd_symbols_cache_add_symbol (cfg->cache, @@ -556,11 +581,8 @@ dkim_module_config (struct rspamd_config *cfg) rspamd_config_add_symbol (cfg, "DKIM_SIGN", 0.0, "DKIM signature fake symbol", "dkim", RSPAMD_SYMBOL_FLAG_IGNORE, 1, 1); - rspamd_config_add_symbol (cfg, - "DKIM_TRACE", 0.0, "DKIM trace symbol", - "policies", RSPAMD_SYMBOL_FLAG_IGNORE, 1, 1); + rspamd_config_add_symbol_group (cfg, "DKIM_SIGN", "dkim"); - rspamd_config_add_symbol_group (cfg, "DKIM_TRACE", "dkim"); } else { msg_err_config ("lua script must return " @@ -584,6 +606,7 @@ dkim_module_load_key_format (lua_State *L, struct rspamd_task *task, hex_hash[rspamd_cryptobox_HASHBYTES * 2 + 1]; rspamd_dkim_sign_key_t *ret; GError *err = NULL; + struct dkim_ctx *dkim_module_ctx = dkim_get_context (task->cfg); memset (hex_hash, 0, sizeof (hex_hash)); rspamd_cryptobox_hash (h, key, keylen, NULL, 0); @@ -618,11 +641,13 @@ lua_dkim_sign_handler (lua_State *L) GError *err = NULL; GString *hdr; const gchar *selector = NULL, *domain = NULL, *key = NULL, *rawkey = NULL, - *headers = NULL, *sign_type_str = NULL, *arc_cv = NULL; + *headers = NULL, *sign_type_str = NULL, *arc_cv = NULL, + *pubkey = NULL; rspamd_dkim_sign_context_t *ctx; rspamd_dkim_sign_key_t *dkim_key; gsize rawlen = 0, keylen = 0; - gboolean no_cache = FALSE; + gboolean no_cache = FALSE, strict_pubkey_check = FALSE; + struct dkim_ctx *dkim_module_ctx; luaL_argcheck (L, lua_type (L, 2) == LUA_TTABLE, 2, "'table' expected"); /* @@ -633,11 +658,13 @@ lua_dkim_sign_handler (lua_State *L) */ if (!rspamd_lua_parse_table_arguments (L, 2, &err, "key=V;rawkey=V;*domain=S;*selector=S;no_cache=B;headers=S;" - "sign_type=S;arc_idx=I;arc_cv=S;expire=I", + "sign_type=S;arc_idx=I;arc_cv=S;expire=I;pubkey=S;" + "strict_pubkey_check=B", &keylen, &key, &rawlen, &rawkey, &domain, &selector, &no_cache, &headers, - &sign_type_str, &arc_idx, &arc_cv, &expire)) { - msg_err_task ("invalid return value from sign condition: %e", + &sign_type_str, &arc_idx, &arc_cv, &expire, &pubkey, + &strict_pubkey_check)) { + msg_err_task ("cannot parse table arguments: %e", err); g_error_free (err); @@ -645,6 +672,8 @@ lua_dkim_sign_handler (lua_State *L) return 1; } + dkim_module_ctx = dkim_get_context (task->cfg); + if (headers == NULL) { headers = dkim_module_ctx->sign_headers; } @@ -766,6 +795,53 @@ lua_dkim_sign_handler (lua_State *L) } } + if (pubkey != NULL) { + /* Also check if private and public keys match */ + rspamd_dkim_key_t *pk; + gsize keylen = strlen (pubkey); + + pk = rspamd_dkim_parse_key (pubkey, &keylen, NULL); + + if (pk == NULL) { + if (strict_pubkey_check) { + msg_err_task ("cannot parse pubkey from string: %s, skip signing", + pubkey); + lua_pushboolean (L, FALSE); + + return 1; + } + else { + msg_warn_task ("cannot parse pubkey from string: %s", + pubkey); + } + } + else { + GError *te = NULL; + + /* We have parsed the key, so try to check keys */ + if (!rspamd_dkim_match_keys (pk, dkim_key, &te)) { + if (strict_pubkey_check) { + msg_err_task ("public key for %s/%s does not match private " + "key: %e, skip signing", + domain, selector, te); + g_error_free (te); + lua_pushboolean (L, FALSE); + rspamd_dkim_key_unref (pk); + + return 1; + } + else { + msg_warn_task ("public key for %s/%s does not match private " + "key: %e", + domain, selector, te); + g_error_free (te); + } + } + + rspamd_dkim_key_unref (pk); + } + } + ctx = rspamd_create_dkim_sign_context (task, dkim_key, DKIM_CANON_RELAXED, DKIM_CANON_RELAXED, headers, sign_type, &err); @@ -805,26 +881,6 @@ lua_dkim_sign_handler (lua_State *L) gint dkim_module_reconfig (struct rspamd_config *cfg) { - struct module_ctx saved_ctx; - - saved_ctx = dkim_module_ctx->ctx; - rspamd_mempool_delete (dkim_module_ctx->dkim_pool); - - if (dkim_module_ctx->dkim_hash) { - rspamd_lru_hash_destroy (dkim_module_ctx->dkim_hash); - } - - if (dkim_module_ctx->dkim_sign_hash) { - rspamd_lru_hash_destroy (dkim_module_ctx->dkim_sign_hash); - } - - memset (dkim_module_ctx, 0, sizeof (*dkim_module_ctx)); - dkim_module_ctx->ctx = saved_ctx; - dkim_module_ctx->dkim_pool = rspamd_mempool_new (rspamd_mempool_suggest_size (), "dkim"); - dkim_module_ctx->sign_headers = default_sign_headers; - dkim_module_ctx->sign_condition_ref = -1; - dkim_module_ctx->max_sigs = DEFAULT_MAX_SIGS; - return dkim_module_config (cfg); } @@ -832,17 +888,27 @@ dkim_module_reconfig (struct rspamd_config *cfg) * Parse strict value for domain in format: 'reject_multiplier:deny_multiplier' */ static gboolean -dkim_module_parse_strict (const gchar *value, gint *allow, gint *deny) +dkim_module_parse_strict (const gchar *value, gdouble *allow, gdouble *deny) { const gchar *colon; - gulong val; + gchar *err = NULL; + gdouble val; + gchar numbuf[64]; colon = strchr (value, ':'); if (colon) { - if (rspamd_strtoul (value, colon - value, &val)) { + rspamd_strlcpy (numbuf, value, + MIN (sizeof (numbuf), (colon - value) + 1)); + val = strtod (numbuf, &err); + + if (err == NULL || *err == '\0') { *deny = val; colon++; - if (rspamd_strtoul (colon, strlen (colon), &val)) { + rspamd_strlcpy (numbuf, colon, sizeof (numbuf)); + err = NULL; + val = strtod (numbuf, &err); + + if (err == NULL || *err == '\0') { *allow = val; return TRUE; } @@ -857,6 +923,7 @@ dkim_module_check (struct dkim_check_result *res) gboolean all_done = TRUE; const gchar *strict_value; struct dkim_check_result *first, *cur = NULL; + struct dkim_ctx *dkim_module_ctx = dkim_get_context (res->task->cfg); first = res->first; @@ -896,7 +963,7 @@ dkim_module_check (struct dkim_check_result *res) if (all_done) { DL_FOREACH (first, cur) { const gchar *symbol = NULL, *trace = NULL; - int symbol_weight = 1; + gdouble symbol_weight = 1.0; if (cur->ctx == NULL) { continue; @@ -953,8 +1020,10 @@ dkim_module_key_handler (rspamd_dkim_key_t *key, { struct dkim_check_result *res = ud; struct rspamd_task *task; + struct dkim_ctx *dkim_module_ctx; task = res->task; + dkim_module_ctx = dkim_get_context (task->cfg); if (key != NULL) { /* @@ -1002,6 +1071,7 @@ dkim_symbol_callback (struct rspamd_task *task, void *unused) struct rspamd_mime_header *rh; struct dkim_check_result *res = NULL, *cur; guint checked = 0, i, *dmarc_checks; + struct dkim_ctx *dkim_module_ctx = dkim_get_context (task->cfg); /* Allow dmarc */ dmarc_checks = rspamd_mempool_get_variable (task->task_pool, @@ -1047,19 +1117,9 @@ dkim_symbol_callback (struct rspamd_task *task, void *unused) continue; } - if (res == NULL) { - res = rspamd_mempool_alloc0 (task->task_pool, sizeof (*res)); - res->prev = res; - res->w = rspamd_session_get_watcher (task->s); - cur = res; - } - else { - cur = rspamd_mempool_alloc0 (task->task_pool, sizeof (*res)); - } - + cur = rspamd_mempool_alloc0 (task->task_pool, sizeof (*cur)); cur->first = res; cur->res = -1; - cur->w = res->w; cur->task = task; cur->mult_allow = 1.0; cur->mult_deny = 1.0; @@ -1087,7 +1147,6 @@ dkim_symbol_callback (struct rspamd_task *task, void *unused) } else { /* Get key */ - cur->ctx = ctx; if (dkim_module_ctx->trusted_only && @@ -1111,14 +1170,23 @@ dkim_symbol_callback (struct rspamd_task *task, void *unused) dkim_module_key_dtor, cur->key); } else { - rspamd_get_dkim_key (ctx, + if (!rspamd_get_dkim_key (ctx, task, dkim_module_key_handler, - cur); + cur)) { + continue; + } } } - if (res != cur) { + if (res == NULL) { + res = cur; + res->first = res; + res->prev = res; + res->w = rspamd_session_get_watcher (task->s); + } + else { + cur->w = res->w; DL_APPEND (res, cur); } @@ -1164,6 +1232,7 @@ dkim_sign_callback (struct rspamd_task *task, void *unused) enum rspamd_dkim_type sign_type = RSPAMD_DKIM_NORMAL; guchar h[rspamd_cryptobox_HASHBYTES], hex_hash[rspamd_cryptobox_HASHBYTES * 2 + 1]; + struct dkim_ctx *dkim_module_ctx = dkim_get_context (task->cfg); if (dkim_module_ctx->sign_condition_ref != -1) { sign = FALSE; @@ -1463,8 +1532,10 @@ dkim_module_lua_on_key (rspamd_dkim_key_t *key, struct rspamd_dkim_lua_verify_cbdata *cbd = ud; struct rspamd_task *task; gint ret; + struct dkim_ctx *dkim_module_ctx; task = cbd->task; + dkim_module_ctx = dkim_get_context (task->cfg); if (key != NULL) { /* @@ -1520,6 +1591,7 @@ lua_dkim_verify_handler (lua_State *L) GError *err = NULL; const gchar *type_str = NULL; enum rspamd_dkim_type type = RSPAMD_DKIM_NORMAL; + struct dkim_ctx *dkim_module_ctx; if (task && sig && lua_isfunction (L, 3)) { if (lua_isstring (L, 4)) { @@ -1543,6 +1615,8 @@ lua_dkim_verify_handler (lua_State *L) } } + dkim_module_ctx = dkim_get_context (task->cfg); + ctx = rspamd_create_dkim_context (sig, task->task_pool, dkim_module_ctx->time_jitter, diff --git a/src/plugins/fuzzy_check.c b/src/plugins/fuzzy_check.c index c4318777f..b5c390328 100644 --- a/src/plugins/fuzzy_check.c +++ b/src/plugins/fuzzy_check.c @@ -58,6 +58,7 @@ #define RSPAMD_FUZZY_PLUGIN_VERSION RSPAMD_FUZZY_VERSION static const gint rspamd_fuzzy_hash_len = 5; +struct fuzzy_ctx; struct fuzzy_mapping { guint64 fuzzy_flag; @@ -91,6 +92,7 @@ struct fuzzy_rule { gboolean short_text_direct_hash; gint learn_condition_cb; struct rspamd_hash_map_helper *skip_map; + struct fuzzy_ctx *ctx; }; struct fuzzy_ctx { @@ -172,7 +174,7 @@ struct fuzzy_cmd_io { struct iovec io; }; -static struct fuzzy_ctx *fuzzy_module_ctx = NULL; + static const char *default_headers = "Subject,Content-Type,Reply-To,X-Mailer"; static void fuzzy_symbol_callback (struct rspamd_task *task, void *unused); @@ -188,19 +190,27 @@ static gint fuzzy_lua_learn_handler (lua_State *L); static gint fuzzy_lua_unlearn_handler (lua_State *L); module_t fuzzy_check_module = { - "fuzzy_check", - fuzzy_check_module_init, - fuzzy_check_module_config, - fuzzy_check_module_reconfig, - fuzzy_attach_controller, - RSPAMD_MODULE_VER + "fuzzy_check", + fuzzy_check_module_init, + fuzzy_check_module_config, + fuzzy_check_module_reconfig, + fuzzy_attach_controller, + RSPAMD_MODULE_VER, + (guint)-1, }; +static inline struct fuzzy_ctx * +fuzzy_get_context (struct rspamd_config *cfg) +{ + return (struct fuzzy_ctx *)g_ptr_array_index (cfg->c_modules, + fuzzy_check_module.ctx_offset); +} + static void parse_flags (struct fuzzy_rule *rule, - struct rspamd_config *cfg, - const ucl_object_t *val, - gint cb_id) + struct rspamd_config *cfg, + const ucl_object_t *val, + gint cb_id) { const ucl_object_t *elt; struct fuzzy_mapping *map; @@ -217,7 +227,7 @@ parse_flags (struct fuzzy_rule *rule, } if (sym != NULL) { map = - rspamd_mempool_alloc (fuzzy_module_ctx->fuzzy_pool, + rspamd_mempool_alloc (cfg->cfg_pool, sizeof (struct fuzzy_mapping)); map->symbol = sym; elt = ucl_object_lookup (val, "flag"); @@ -257,7 +267,7 @@ parse_flags (struct fuzzy_rule *rule, static GPtrArray * -parse_mime_types (const gchar *str) +parse_mime_types (struct rspamd_config *cfg, const gchar *str) { gchar **strvec, *p; gint num, i; @@ -272,24 +282,24 @@ parse_mime_types (const gchar *str) g_strstrip (strvec[i]); if ((p = strchr (strvec[i], '/')) != NULL) { - type = rspamd_mempool_alloc (fuzzy_module_ctx->fuzzy_pool, + type = rspamd_mempool_alloc (cfg->cfg_pool, sizeof (struct fuzzy_mime_type)); type->type_re = rspamd_regexp_from_glob (strvec[i], p - strvec[i], NULL); type->subtype_re = rspamd_regexp_from_glob (p + 1, 0, NULL); - rspamd_mempool_add_destructor (fuzzy_module_ctx->fuzzy_pool, + rspamd_mempool_add_destructor (cfg->cfg_pool, (rspamd_mempool_destruct_t)rspamd_regexp_unref, type->type_re); - rspamd_mempool_add_destructor (fuzzy_module_ctx->fuzzy_pool, + rspamd_mempool_add_destructor (cfg->cfg_pool, (rspamd_mempool_destruct_t)rspamd_regexp_unref, type->subtype_re); g_ptr_array_add (res, type); } else { - type = rspamd_mempool_alloc (fuzzy_module_ctx->fuzzy_pool, + type = rspamd_mempool_alloc (cfg->cfg_pool, sizeof (struct fuzzy_mime_type)); type->type_re = rspamd_regexp_from_glob (strvec[i], 0, NULL); - rspamd_mempool_add_destructor (fuzzy_module_ctx->fuzzy_pool, + rspamd_mempool_add_destructor (cfg->cfg_pool, (rspamd_mempool_destruct_t)rspamd_regexp_unref, type->type_re); type->subtype_re = NULL; @@ -303,7 +313,7 @@ parse_mime_types (const gchar *str) } static GPtrArray * -parse_fuzzy_headers (const gchar *str) +parse_fuzzy_headers (struct rspamd_config *cfg, const gchar *str) { gchar **strvec; gint num, i; @@ -316,7 +326,7 @@ parse_fuzzy_headers (const gchar *str) for (i = 0; i < num; i++) { g_strstrip (strvec[i]); g_ptr_array_add (res, rspamd_mempool_strdup ( - fuzzy_module_ctx->fuzzy_pool, strvec[i])); + cfg->cfg_pool, strvec[i])); } g_strfreev (strvec); @@ -412,6 +422,7 @@ fuzzy_parse_rule (struct rspamd_config *cfg, const ucl_object_t *obj, struct fuzzy_rule *rule; ucl_object_iter_t it = NULL; const char *k = NULL, *key_str = NULL, *shingles_key_str = NULL, *lua_script; + struct fuzzy_ctx *fuzzy_module_ctx = fuzzy_get_context (cfg); if (obj->type != UCL_OBJECT) { msg_err_config ("invalid rule definition"); @@ -419,20 +430,19 @@ fuzzy_parse_rule (struct rspamd_config *cfg, const ucl_object_t *obj, } rule = fuzzy_rule_new (fuzzy_module_ctx->default_symbol, - fuzzy_module_ctx->fuzzy_pool); + cfg->cfg_pool); + rule->ctx = fuzzy_module_ctx; rule->learn_condition_cb = -1; rule->alg = RSPAMD_SHINGLES_OLD; + rule->skip_map = NULL; if ((value = ucl_object_lookup (obj, "skip_hashes")) != NULL) { rspamd_map_add_from_ucl (cfg, value, - "Fuzzy hashes whitelist", rspamd_kv_list_read, rspamd_kv_list_fin, - (void **)&rule->skip_map); - rspamd_mempool_add_destructor (fuzzy_module_ctx->fuzzy_pool, - (rspamd_mempool_destruct_t)rspamd_map_helper_destroy_radix, - rule->skip_map); - } - else { - rule->skip_map = NULL; + "Fuzzy hashes whitelist", + rspamd_kv_list_read, + rspamd_kv_list_fin, + rspamd_kv_list_dtor, + (void **)&rule->skip_map); } if ((value = ucl_object_lookup (obj, "mime_types")) != NULL) { @@ -443,7 +453,7 @@ fuzzy_parse_rule (struct rspamd_config *cfg, const ucl_object_t *obj, guint i; gpointer ptr; - tmp = parse_mime_types (ucl_obj_tostring (cur)); + tmp = parse_mime_types (cfg, ucl_obj_tostring (cur)); if (tmp) { if (rule->mime_types) { @@ -460,7 +470,7 @@ fuzzy_parse_rule (struct rspamd_config *cfg, const ucl_object_t *obj, } if (rule->mime_types) { - rspamd_mempool_add_destructor (fuzzy_module_ctx->fuzzy_pool, + rspamd_mempool_add_destructor (cfg->cfg_pool, rspamd_ptr_array_free_hard, rule->mime_types); } } @@ -473,7 +483,7 @@ fuzzy_parse_rule (struct rspamd_config *cfg, const ucl_object_t *obj, guint i; gpointer ptr; - tmp = parse_fuzzy_headers (ucl_obj_tostring (cur)); + tmp = parse_fuzzy_headers (cfg, ucl_obj_tostring (cur)); if (tmp) { if (rule->fuzzy_headers) { @@ -490,11 +500,11 @@ fuzzy_parse_rule (struct rspamd_config *cfg, const ucl_object_t *obj, } } else { - rule->fuzzy_headers = parse_fuzzy_headers (default_headers); + rule->fuzzy_headers = parse_fuzzy_headers (cfg, default_headers); } if (rule->fuzzy_headers != NULL) { - rspamd_mempool_add_destructor (fuzzy_module_ctx->fuzzy_pool, + rspamd_mempool_add_destructor (cfg->cfg_pool, (rspamd_mempool_destruct_t) rspamd_ptr_array_free_hard, rule->fuzzy_headers); } @@ -580,7 +590,7 @@ fuzzy_parse_rule (struct rspamd_config *cfg, const ucl_object_t *obj, if ((value = ucl_object_lookup (obj, "servers")) != NULL) { rule->servers = rspamd_upstreams_create (cfg->ups_ctx); - rspamd_mempool_add_destructor (fuzzy_module_ctx->fuzzy_pool, + rspamd_mempool_add_destructor (cfg->cfg_pool, (rspamd_mempool_destruct_t)rspamd_upstreams_destroy, rule->servers); if (!rspamd_upstreams_from_ucl (rule->servers, value, DEFAULT_PORT, NULL)) { @@ -689,7 +699,7 @@ fuzzy_parse_rule (struct rspamd_config *cfg, const ucl_object_t *obj, rule->algorithm_str); } - rspamd_mempool_add_destructor (fuzzy_module_ctx->fuzzy_pool, fuzzy_free_rule, + rspamd_mempool_add_destructor (cfg->cfg_pool, fuzzy_free_rule, rule); return 0; @@ -698,17 +708,27 @@ fuzzy_parse_rule (struct rspamd_config *cfg, const ucl_object_t *obj, gint fuzzy_check_module_init (struct rspamd_config *cfg, struct module_ctx **ctx) { - if (fuzzy_module_ctx == NULL) { - fuzzy_module_ctx = g_malloc0 (sizeof (struct fuzzy_ctx)); + struct fuzzy_ctx *fuzzy_module_ctx; - fuzzy_module_ctx->fuzzy_pool = rspamd_mempool_new (rspamd_mempool_suggest_size (), NULL); - /* TODO: this should match rules count actually */ - fuzzy_module_ctx->keypairs_cache = rspamd_keypair_cache_new (32); - fuzzy_module_ctx->fuzzy_rules = g_ptr_array_new (); - } + fuzzy_module_ctx = rspamd_mempool_alloc0 (cfg->cfg_pool, + sizeof (struct fuzzy_ctx)); + fuzzy_module_ctx->fuzzy_pool = rspamd_mempool_new (rspamd_mempool_suggest_size (), NULL); + /* TODO: this should match rules count actually */ + fuzzy_module_ctx->keypairs_cache = rspamd_keypair_cache_new (32); + fuzzy_module_ctx->fuzzy_rules = g_ptr_array_new (); fuzzy_module_ctx->cfg = cfg; + rspamd_mempool_add_destructor (cfg->cfg_pool, + (rspamd_mempool_destruct_t)rspamd_mempool_delete, + fuzzy_module_ctx->fuzzy_pool); + rspamd_mempool_add_destructor (cfg->cfg_pool, + (rspamd_mempool_destruct_t)rspamd_keypair_cache_destroy, + fuzzy_module_ctx->keypairs_cache); + rspamd_mempool_add_destructor (cfg->cfg_pool, + (rspamd_mempool_destruct_t)rspamd_ptr_array_free_hard, + fuzzy_module_ctx->fuzzy_rules); + *ctx = (struct module_ctx *)fuzzy_module_ctx; rspamd_rcl_add_doc_by_path (cfg, @@ -978,6 +998,7 @@ fuzzy_check_module_config (struct rspamd_config *cfg) ucl_object_iter_t it; gint res = TRUE, cb_id, nrules = 0; lua_State *L = cfg->lua_state; + struct fuzzy_ctx *fuzzy_module_ctx = fuzzy_get_context (cfg); if (!rspamd_config_is_module_enabled (cfg, "fuzzy_check")) { return TRUE; @@ -1150,19 +1171,6 @@ fuzzy_check_module_config (struct rspamd_config *cfg) gint fuzzy_check_module_reconfig (struct rspamd_config *cfg) { - struct module_ctx saved_ctx; - - saved_ctx = fuzzy_module_ctx->ctx; - rspamd_mempool_delete (fuzzy_module_ctx->fuzzy_pool); - rspamd_keypair_cache_destroy (fuzzy_module_ctx->keypairs_cache); - g_ptr_array_free (fuzzy_module_ctx->fuzzy_rules, TRUE); - memset (fuzzy_module_ctx, 0, sizeof (*fuzzy_module_ctx)); - fuzzy_module_ctx->ctx = saved_ctx; - fuzzy_module_ctx->fuzzy_pool = rspamd_mempool_new (rspamd_mempool_suggest_size (), NULL); - fuzzy_module_ctx->cfg = cfg; - fuzzy_module_ctx->fuzzy_rules = g_ptr_array_new (); - fuzzy_module_ctx->keypairs_cache = rspamd_keypair_cache_new (32); - return fuzzy_check_module_config (cfg); } @@ -1213,10 +1221,11 @@ fuzzy_encrypt_cmd (struct fuzzy_rule *rule, memcpy (hdr->pubkey, pk, MIN (pklen, sizeof (hdr->pubkey))); pk = rspamd_pubkey_get_pk (rule->peer_key, &pklen); memcpy (hdr->key_id, pk, MIN (sizeof (hdr->key_id), pklen)); - rspamd_keypair_cache_process (fuzzy_module_ctx->keypairs_cache, + rspamd_keypair_cache_process (rule->ctx->keypairs_cache, rule->local_key, rule->peer_key); rspamd_cryptobox_encrypt_nm_inplace (data, datalen, - hdr->nonce, rspamd_pubkey_get_nm (rule->peer_key), hdr->mac, + hdr->nonce, rspamd_pubkey_get_nm (rule->peer_key, rule->local_key), + hdr->mac, rspamd_pubkey_alg (rule->peer_key)); } @@ -1765,13 +1774,13 @@ fuzzy_process_reply (guchar **pos, gint *r, GPtrArray *req, *r -= required_size; /* Try to decrypt reply */ - rspamd_keypair_cache_process (fuzzy_module_ctx->keypairs_cache, + rspamd_keypair_cache_process (rule->ctx->keypairs_cache, rule->local_key, rule->peer_key); if (!rspamd_cryptobox_decrypt_nm_inplace ((guchar *)&encrep.rep, sizeof (encrep.rep), encrep.hdr.nonce, - rspamd_pubkey_get_nm (rule->peer_key), + rspamd_pubkey_get_nm (rule->peer_key, rule->local_key), encrep.hdr.mac, rspamd_pubkey_alg (rule->peer_key))) { msg_info ("cannot decrypt reply"); @@ -2169,7 +2178,7 @@ fuzzy_check_io_callback (gint fd, short what, void *arg) session->state == 1 ? "read" : "write", errno, strerror (errno)); - rspamd_upstream_fail (session->server); + rspamd_upstream_fail (session->server, FALSE); rspamd_session_remove_event (session->task->s, fuzzy_io_fin, session); } else { @@ -2203,12 +2212,12 @@ fuzzy_check_timer_callback (gint fd, short what, void *arg) } } - if (session->retransmits >= fuzzy_module_ctx->retransmits) { + if (session->retransmits >= session->rule->ctx->retransmits) { msg_err_task ("got IO timeout with server %s(%s), after %d retransmits", rspamd_upstream_name (session->server), rspamd_inet_address_to_string_pretty (session->addr), session->retransmits); - rspamd_upstream_fail (session->server); + rspamd_upstream_fail (session->server, FALSE); rspamd_session_remove_event (session->task->s, fuzzy_io_fin, session); } else { @@ -2413,7 +2422,7 @@ fuzzy_controller_io_callback (gint fd, short what, void *arg) rspamd_upstream_name (session->server), rspamd_inet_address_to_string_pretty (session->addr), errno, strerror (errno)); - rspamd_upstream_fail (session->server); + rspamd_upstream_fail (session->server, FALSE); } /* @@ -2509,8 +2518,8 @@ fuzzy_controller_timer_callback (gint fd, short what, void *arg) task = session->task; - if (session->retransmits >= fuzzy_module_ctx->retransmits) { - rspamd_upstream_fail (session->server); + if (session->retransmits >= session->rule->ctx->retransmits) { + rspamd_upstream_fail (session->server, FALSE); msg_err_task_check ("got IO timeout with server %s(%s), " "after %d retransmits", rspamd_upstream_name (session->server), @@ -2582,7 +2591,7 @@ fuzzy_generate_commands (struct rspamd_task *task, struct fuzzy_rule *rule, min_bytes = rule->min_bytes; } else { - min_bytes = fuzzy_module_ctx->min_bytes; + min_bytes = rule->ctx->min_bytes; } if (c == FUZZY_STAT) { @@ -2606,7 +2615,7 @@ fuzzy_generate_commands (struct rspamd_task *task, struct fuzzy_rule *rule, } /* Check length of part */ - fac = fuzzy_module_ctx->text_multiplier * part->content->len; + fac = rule->ctx->text_multiplier * part->content->len; if ((double)min_bytes > fac) { if (!rule->short_text_direct_hash) { msg_info_task ( @@ -2616,7 +2625,7 @@ fuzzy_generate_commands (struct rspamd_task *task, struct fuzzy_rule *rule, task->message_id, min_bytes, fac, part->content->len, - fuzzy_module_ctx->text_multiplier); + rule->ctx->text_multiplier); continue; } else { @@ -2627,7 +2636,7 @@ fuzzy_generate_commands (struct rspamd_task *task, struct fuzzy_rule *rule, task->message_id, min_bytes, fac, part->content->len, - fuzzy_module_ctx->text_multiplier); + rule->ctx->text_multiplier); short_text = TRUE; } } @@ -2639,15 +2648,15 @@ fuzzy_generate_commands (struct rspamd_task *task, struct fuzzy_rule *rule, continue; } - if (fuzzy_module_ctx->min_hash_len != 0 && + if (rule->ctx->min_hash_len != 0 && part->normalized_words->len < - fuzzy_module_ctx->min_hash_len) { + rule->ctx->min_hash_len) { if (!rule->short_text_direct_hash) { msg_info_task ( "<%s>, part hash is shorter than %d symbols, " "skip fuzzy check", task->message_id, - fuzzy_module_ctx->min_hash_len); + rule->ctx->min_hash_len); continue; } else { @@ -2655,7 +2664,7 @@ fuzzy_generate_commands (struct rspamd_task *task, struct fuzzy_rule *rule, "<%s>, part hash is shorter than %d symbols, " "use direct hash", task->message_id, - fuzzy_module_ctx->min_hash_len); + rule->ctx->min_hash_len); short_text = TRUE; } } @@ -2703,10 +2712,10 @@ fuzzy_generate_commands (struct rspamd_task *task, struct fuzzy_rule *rule, * - min bytes */ - if ((fuzzy_module_ctx->min_height == 0 || - image->height >= fuzzy_module_ctx->min_height) && - (fuzzy_module_ctx->min_width == 0 || - image->width >= fuzzy_module_ctx->min_width) && + if ((rule->ctx->min_height == 0 || + image->height >= rule->ctx->min_height) && + (rule->ctx->min_width == 0 || + image->width >= rule->ctx->min_width) && (min_bytes == 0 || mime_part->parsed_data.len >= min_bytes)) { io = fuzzy_cmd_from_data_part (rule, c, flag, value, @@ -2832,7 +2841,7 @@ register_fuzzy_client_call (struct rspamd_task *task, rspamd_inet_address_to_string_pretty (addr), errno, strerror (errno)); - rspamd_upstream_fail (selected); + rspamd_upstream_fail (selected, FALSE); g_ptr_array_free (commands, TRUE); } else { @@ -2840,7 +2849,7 @@ register_fuzzy_client_call (struct rspamd_task *task, session = rspamd_mempool_alloc0 (task->task_pool, sizeof (struct fuzzy_client_session)); - msec_to_tv (fuzzy_module_ctx->io_timeout, &session->tv); + msec_to_tv (rule->ctx->io_timeout, &session->tv); session->state = 0; session->commands = commands; session->task = task; @@ -2875,6 +2884,7 @@ fuzzy_symbol_callback (struct rspamd_task *task, void *unused) struct fuzzy_rule *rule; guint i; GPtrArray *commands; + struct fuzzy_ctx *fuzzy_module_ctx = fuzzy_get_context (task->cfg); if (!fuzzy_module_ctx->enabled) { return; @@ -2906,6 +2916,7 @@ fuzzy_stat_command (struct rspamd_task *task) struct fuzzy_rule *rule; guint i; GPtrArray *commands; + struct fuzzy_ctx *fuzzy_module_ctx = fuzzy_get_context (task->cfg); if (!fuzzy_module_ctx->enabled) { return; @@ -2933,6 +2944,7 @@ register_fuzzy_controller_call (struct rspamd_http_connection_entry *entry, struct rspamd_controller_session *session = entry->ud; gint sock; gint ret = -1; + struct fuzzy_ctx *fuzzy_module_ctx = fuzzy_get_context (task->cfg); /* Get upstream */ @@ -2943,7 +2955,7 @@ register_fuzzy_controller_call (struct rspamd_http_connection_entry *entry, if ((sock = rspamd_inet_address_connect (addr, SOCK_DGRAM, TRUE)) == -1) { - rspamd_upstream_fail (selected); + rspamd_upstream_fail (selected, TRUE); } else { s = @@ -2995,6 +3007,7 @@ fuzzy_process_handler (struct rspamd_http_connection_entry *conn_ent, GString *tb; lua_State *L; gint r, *saved, rules = 0, err_idx; + struct fuzzy_ctx *fuzzy_module_ctx; /* Prepare task */ task = rspamd_task_new (session->wrk, session->cfg, NULL, session->lang_det); @@ -3002,6 +3015,7 @@ fuzzy_process_handler (struct rspamd_http_connection_entry *conn_ent, task->ev_base = conn_ent->rt->ev_base; saved = rspamd_mempool_alloc0 (session->pool, sizeof (gint)); err = rspamd_mempool_alloc0 (session->pool, sizeof (GError *)); + fuzzy_module_ctx = fuzzy_get_context (ctx->cfg); if (!is_hash) { /* Allocate message from string */ @@ -3190,6 +3204,7 @@ fuzzy_controller_handler (struct rspamd_http_connection_entry *conn_ent, { const rspamd_ftok_t *arg; glong value = 1, flag = 0, send_flags = 0; + struct fuzzy_ctx *fuzzy_module_ctx = (struct fuzzy_ctx *)ctx; if (!fuzzy_module_ctx->enabled) { msg_err ("fuzzy_check module is not enabled"); @@ -3303,14 +3318,14 @@ fuzzy_check_send_lua_learn (struct fuzzy_rule *rule, if ((sock = rspamd_inet_address_connect (addr, SOCK_DGRAM, TRUE)) == -1) { - rspamd_upstream_fail (selected); + rspamd_upstream_fail (selected, TRUE); } else { s = rspamd_mempool_alloc0 (task->task_pool, sizeof (struct fuzzy_learn_session)); - msec_to_tv (fuzzy_module_ctx->io_timeout, &s->tv); + msec_to_tv (rule->ctx->io_timeout, &s->tv); s->task = task; s->addr = addr; s->commands = commands; @@ -3353,6 +3368,7 @@ fuzzy_check_lua_process_learn (struct rspamd_task *task, GError **err; GPtrArray *commands; gint *saved, rules = 0; + struct fuzzy_ctx *fuzzy_module_ctx = fuzzy_get_context (task->cfg); saved = rspamd_mempool_alloc0 (task->task_pool, sizeof (gint)); err = rspamd_mempool_alloc0 (task->task_pool, sizeof (GError *)); @@ -3419,6 +3435,7 @@ fuzzy_lua_learn_handler (lua_State *L) struct rspamd_task *task = lua_check_task (L, 1); guint flag = 0, weight = 1.0, send_flags = 0; const gchar *symbol; + struct fuzzy_ctx *fuzzy_module_ctx = fuzzy_get_context (task->cfg); if (task) { if (lua_type (L, 2) == LUA_TNUMBER) { @@ -3496,6 +3513,7 @@ fuzzy_lua_unlearn_handler (lua_State *L) struct rspamd_task *task = lua_check_task (L, 1); guint flag = 0, weight = 1.0, send_flags = 0; const gchar *symbol; + struct fuzzy_ctx *fuzzy_module_ctx = fuzzy_get_context (task->cfg); if (task) { if (lua_type (L, 2) == LUA_TNUMBER) { diff --git a/src/plugins/lua/antivirus.lua b/src/plugins/lua/antivirus.lua index 946400743..6adabff3c 100644 --- a/src/plugins/lua/antivirus.lua +++ b/src/plugins/lua/antivirus.lua @@ -33,6 +33,8 @@ antivirus { clamav { # If set force this action if any virus is found (default unset: no action is forced) # action = "reject"; + # If set, then rejection message is set to this value (mention single quotes) + # message = '${SCANNER}: virus found: "${VIRUS}"'; # if `true` only messages with non-image attachments will be checked (default true) attachments_only = true; # If `max_size` is set, messages > n bytes in size are not scanned @@ -64,6 +66,8 @@ antivirus { return end +local default_message = '${SCANNER}: virus found: "${VIRUS}"' + local function match_patterns(default_sym, found, patterns) if type(patterns) ~= 'table' then return default_sym end if not patterns[1] then @@ -113,7 +117,10 @@ local function yield_result(task, rule, vname) vname = table.concat(vname, '; ') end task:set_pre_result(rule['action'], - string.format('%s: virus found: "%s"', rule['type'], vname)) + lua_util.template(rule.message or 'Rejected', { + SCANNER = rule['type'], + VIRUS = vname, + })) end end @@ -125,6 +132,7 @@ local function clamav_config(opts) timeout = 15.0, retransmits = 2, cache_expire = 3600, -- expire redis in one hour + message = default_message, } for k,v in pairs(opts) do @@ -162,6 +170,7 @@ local function fprot_config(opts) log_clean = false, retransmits = 2, cache_expire = 3600, -- expire redis in one hour + message = default_message, } for k,v in pairs(opts) do @@ -199,6 +208,7 @@ local function sophos_config(opts) log_clean = false, retransmits = 2, cache_expire = 3600, -- expire redis in one hour + message = default_message, } for k,v in pairs(opts) do @@ -237,6 +247,7 @@ local function savapi_config(opts) timeout = 15.0, retransmits = 2, cache_expire = 3600, -- expire redis in one hour + message = default_message, } for k,v in pairs(opts) do @@ -266,36 +277,23 @@ local function savapi_config(opts) return nil end -local function message_not_too_large(task, rule) +local function message_not_too_large(task, content, rule) local max_size = tonumber(rule['max_size']) if not max_size then return true end - if task:get_size() > max_size then + if #content > max_size then rspamd_logger.infox("skip %s AV check as it is too large: %s (%s is allowed)", - rule.type, task:get_size(), max_size) + rule.type, #content, max_size) return false end return true end -local function need_av_check(task, rule) - if rule['attachments_only'] then - for _,p in ipairs(task:get_parts()) do - if p:get_filename() and not p:is_image() then - return message_not_too_large(task, rule) - end - end - - rspamd_logger.infox("skip %s AV check as there are no attachments in a message", - rule.type) - - return false - else - return message_not_too_large(task, rule) - end +local function need_av_check(task, content, rule) + return message_not_too_large(task, content, rule) end -local function check_av_cache(task, rule, fn) - local key = task:get_digest() +local function check_av_cache(task, digest, rule, fn) + local key = digest local function redis_av_cb(err, data) if data and type(data) == 'string' then @@ -334,8 +332,8 @@ local function check_av_cache(task, rule, fn) return false end -local function save_av_cache(task, rule, to_save) - local key = task:get_digest() +local function save_av_cache(task, digest, rule, to_save) + local key = digest local function redis_set_cb(err) -- Do nothing @@ -367,14 +365,15 @@ local function save_av_cache(task, rule, to_save) return false end -local function fprot_check(task, rule) +local function fprot_check(task, content, digest, rule) local function fprot_check_uncached () local upstream = rule.upstreams:get_upstream_round_robin() local addr = upstream:get_addr() local retransmits = rule.retransmits local scan_id = task:get_queue_id() if not scan_id then scan_id = task:get_uid() end - local header = string.format('SCAN STREAM %s SIZE %d\n', scan_id, task:get_size()) + local header = string.format('SCAN STREAM %s SIZE %d\n', scan_id, + #content) local footer = '\n' local function fprot_callback(err, data) @@ -391,7 +390,7 @@ local function fprot_check(task, rule) port = addr:get_port(), timeout = rule['timeout'], callback = fprot_callback, - data = { header, task:get_content(), footer }, + data = { header, content, footer }, stop_pattern = '\n' }) else @@ -426,7 +425,7 @@ local function fprot_check(task, rule) end end if cached then - save_av_cache(task, rule, cached) + save_av_cache(task, digest, rule, cached) end end end @@ -437,13 +436,13 @@ local function fprot_check(task, rule) port = addr:get_port(), timeout = rule['timeout'], callback = fprot_callback, - data = { header, task:get_content(), footer }, + data = { header, content, footer }, stop_pattern = '\n' }) end - if need_av_check(task, rule) then - if check_av_cache(task, rule, fprot_check_uncached) then + if need_av_check(task, content, rule) then + if check_av_cache(task, digest, rule, fprot_check_uncached) then return else fprot_check_uncached() @@ -451,13 +450,13 @@ local function fprot_check(task, rule) end end -local function clamav_check(task, rule) +local function clamav_check(task, content, digest, rule) local function clamav_check_uncached () local upstream = rule.upstreams:get_upstream_round_robin() local addr = upstream:get_addr() local retransmits = rule.retransmits local header = rspamd_util.pack("c9 c1 >I4", "zINSTREAM", "\0", - task:get_size()) + #content) local footer = rspamd_util.pack(">I4", 0) local function clamav_callback(err, data) @@ -475,7 +474,7 @@ local function clamav_check(task, rule) port = addr:get_port(), timeout = rule['timeout'], callback = clamav_callback, - data = { header, task:get_content(), footer }, + data = { header, content, footer }, stop_pattern = '\0' }) else @@ -511,7 +510,7 @@ local function clamav_check(task, rule) end end if cached then - save_av_cache(task, rule, cached) + save_av_cache(task, digest, rule, cached) end end end @@ -522,13 +521,13 @@ local function clamav_check(task, rule) port = addr:get_port(), timeout = rule['timeout'], callback = clamav_callback, - data = { header, task:get_content(), footer }, + data = { header, content, footer }, stop_pattern = '\0' }) end - if need_av_check(task, rule) then - if check_av_cache(task, rule, clamav_check_uncached) then + if need_av_check(task, content, rule) then + if check_av_cache(task, digest, rule, clamav_check_uncached) then return else clamav_check_uncached() @@ -536,13 +535,13 @@ local function clamav_check(task, rule) end end -local function sophos_check(task, rule) +local function sophos_check(task, content, digest, rule) local function sophos_check_uncached () local upstream = rule.upstreams:get_upstream_round_robin() local addr = upstream:get_addr() local retransmits = rule.retransmits local protocol = 'SSSP/1.0\n' - local streamsize = string.format('SCANDATA %d\n', task:get_size()) + local streamsize = string.format('SCANDATA %d\n', #content) local bye = 'BYE\n' local function sophos_callback(err, data, conn) @@ -560,7 +559,7 @@ local function sophos_check(task, rule) port = addr:get_port(), timeout = rule['timeout'], callback = sophos_callback, - data = { protocol, streamsize, task:get_content(), bye } + data = { protocol, streamsize, content, bye } }) else rspamd_logger.errx(task, 'failed to scan, maximum retransmits exceed') @@ -578,24 +577,24 @@ local function sophos_check(task, rule) local vname = string.match(data, 'VIRUS (%S+) ') if vname then yield_result(task, rule, vname) - save_av_cache(task, rule, vname) + save_av_cache(task, digest, rule, vname) else if string.find(data, 'DONE OK') then if rule['log_clean'] then rspamd_logger.infox(task, '%s [%s]: message is clean', rule['symbol'], rule['type']) end - save_av_cache(task, rule, 'OK') + save_av_cache(task, digest, rule, 'OK') elseif string.find(data, 'FAIL 0212') then if rule['savdi_report_encrypted'] then rspamd_logger.infox(task, 'Message is ENCRYPTED (0212 SOPHOS_SAVI_ERROR_FILE_ENCRYPTED): %s', data) yield_result(task, rule, "SAVDI_FILE_ENCRYPTED") - save_av_cache(task, rule, "SAVDI_FILE_ENCRYPTED") + save_av_cache(task, digest, rule, "SAVDI_FILE_ENCRYPTED") end elseif string.find(data, 'REJ 4') then if rule['savdi_report_oversize'] then rspamd_logger.infox(task, 'Message is OVERSIZED (SSSP reject code 4): %s', data) - yield_result(task, rule, "SAVDI_FILE_OVERSIZED") - save_av_cache(task, rule, "SAVDI_FILE_OVERSIZED") + yield_result(task, digest, rule, "SAVDI_FILE_OVERSIZED") + save_av_cache(task, digest, rule, "SAVDI_FILE_OVERSIZED") end elseif string.find(data, 'REJ 1') then rspamd_logger.errx(task, 'SAVDI (Protocol error (REJ 1)): %s', data) @@ -615,12 +614,12 @@ local function sophos_check(task, rule) port = addr:get_port(), timeout = rule['timeout'], callback = sophos_callback, - data = { protocol, streamsize, task:get_content(), bye } + data = { protocol, streamsize, content, bye } }) end - if need_av_check(task, rule) then - if check_av_cache(task, rule, sophos_check_uncached) then + if need_av_check(task, content, rule) then + if check_av_cache(task, digest, rule, sophos_check_uncached) then return else sophos_check_uncached() @@ -628,7 +627,7 @@ local function sophos_check(task, rule) end end -local function savapi_check(task, rule) +local function savapi_check(task, content, digest, rule) local function savapi_check_uncached () local upstream = rule.upstreams:get_upstream_round_robin() local addr = upstream:get_addr() @@ -653,7 +652,7 @@ local function savapi_check(task, rule) end yield_result(task, rule, vname) - save_av_cache(task, rule, vname) + save_av_cache(task, digest, rule, vname) end if conn then conn:close() @@ -669,7 +668,7 @@ local function savapi_check(task, rule) if rule['log_clean'] then rspamd_logger.infox(task, '%s: message is clean', rule['type']) end - save_av_cache(task, rule, 'OK') + save_av_cache(task, digest, rule, 'OK') conn:add_write(savapi_fin_cb, 'QUIT\n') -- Terminal response - infected @@ -761,8 +760,8 @@ local function savapi_check(task, rule) }) end - if need_av_check(task, rule) then - if check_av_cache(task, rule, savapi_check_uncached) then + if need_av_check(task, content, rule) then + if check_av_cache(task, digest, rule, savapi_check_uncached) then return else savapi_check_uncached() @@ -844,7 +843,21 @@ local function add_antivirus_rule(sym, opts) end return function(task) - return cfg.check(task, rule) + if rule.attachments_only then + local parts = task:get_parts() or {} + + for _,p in ipairs(parts) do + if not p:is_image() and not p:is_text() and not p:is_multipart() then + local content = p:get_content() + + if content and #content > 0 then + cfg.check(task, content, p:get_digest(), rule) + end + end + end + else + cfg.check(task, task:get_content(), task:get_digest(), rule) + end end end diff --git a/src/plugins/lua/arc.lua b/src/plugins/lua/arc.lua index b240b3b3c..3ae035b44 100644 --- a/src/plugins/lua/arc.lua +++ b/src/plugins/lua/arc.lua @@ -263,9 +263,50 @@ local function arc_callback(task) end end - -- Now we can verify all signatures + --[[ + 1. Collect all ARC Sets currently attached to the message. If there + are none, the Chain Validation Status is "none" and the algorithm + stops here. The maximum number of ARC Sets that can be attached + to a message is 50. If more than the maximum number exist the + Chain Validation Status is "fail" and the algorithm stops here. + In the following algorithm, the maximum ARC instance value is + referred to as "N". + + 2. If the Chain Validation Status of the highest instance value ARC + Set is "fail", then the Chain Validation status is "fail" and the + algorithm stops here. + + 3. Validate the structure of the Authenticated Received Chain. A + valid ARC has the following conditions: + + 1. Each ARC Set MUST contain exactly one each of the three ARC + header fields (AAR, AMS, and AS). + + 2. The instance values of the ARC Sets MUST form a continuous + sequence from 1..N with no gaps or repetition. + + 3. The "cv" value for all ARC-Seal header fields must be non- + failing. For instance values > 1, the value must be "pass". + For instance value = 1, the value must be "none". + + * If any of these conditions are not met, the Chain Validation + Status is "fail" and the algorithm stops here. + + 4. Validate the AMS with the greatest instance value (most recent). + If validation fails, then the Chain Validation Status is "fail" + and the algorithm stops here. + + 5 - 7. Optional, not implemented + 8. Validate each AS beginning with the greatest instance value and + proceeding in decreasing order to the AS with the instance value + of 1. If any AS fails to validate, the Chain Validation Status + is "fail" and the algorithm stops here. + 9. If the algorithm reaches this step, then the Chain Validation + Status is "pass", and the algorithm is complete. + ]]-- + local processed = 0 - local sig = cbdata.sigs[#cbdata.sigs] + local sig = cbdata.sigs[#cbdata.sigs] -- last AMS local ret,err = dkim_verify(task, sig.header, arc_signature_cb, 'arc-sign') if not ret then @@ -510,10 +551,16 @@ local function arc_signing_cb(task) else if (p.key and p.selector) then p.key = lua_util.template(p.key, {domain = p.domain, selector = p.selector}) - if not rspamd_util.file_exists(p.key) then - rspamd_logger.debugm(N, task, 'file %s does not exists', p.key) + local exists,err = rspamd_util.file_exists(p.key) + if not exists then + if err and err == 'No such file or directory' then + rspamd_logger.debugm(N, task, 'cannot read key from %s: %s', p.key, err) + else + rspamd_logger.warnx(N, task, 'cannot read key from %s: %s', p.key, err) + end return false end + local dret, hdr = dkim_sign(task, p) if dret then return arc_sign_seal(task, p, hdr) diff --git a/src/plugins/lua/asn.lua b/src/plugins/lua/asn.lua index 61572a600..86f1c42d2 100644 --- a/src/plugins/lua/asn.lua +++ b/src/plugins/lua/asn.lua @@ -33,6 +33,7 @@ local options = { symbol = 'ASN', expire = 86400, -- 1 day by default key_prefix = 'rasn', + check_local = false, } local rspamd_re = rspamd_regexp.create_cached("[\\|\\s]") @@ -78,7 +79,7 @@ local function asn_check(task) end local ip = task:get_from_ip() - if not (ip and ip:is_valid()) then return end + if not (ip and ip:is_valid()) or (not options.check_local and ip:is_local()) then return end asn_check_func[options['provider_type']](ip) end diff --git a/src/plugins/lua/bayes_expiry.lua b/src/plugins/lua/bayes_expiry.lua index bba2969c6..9495cf0cd 100644 --- a/src/plugins/lua/bayes_expiry.lua +++ b/src/plugins/lua/bayes_expiry.lua @@ -366,7 +366,7 @@ local function expire_step(cls, ev_base, worker) data[5] } logger.infox(rspamd_config, - [[finished expiry %s%s: %s items checked, %s significant (%s %s), %s insignificant(%s %s), %s common (%s discriminated), %s infrequent (%s %s), %s mean, %s std]], + [[finished expiry %s%s: %s items checked, %s significant (%s %s), %s insignificant (%s %s), %s common (%s discriminated), %s infrequent (%s %s), %s mean, %s std]], lutil.unpack(d)) end log_stat(false) diff --git a/src/plugins/lua/clickhouse.lua b/src/plugins/lua/clickhouse.lua index 82762edfe..b54c1a216 100644 --- a/src/plugins/lua/clickhouse.lua +++ b/src/plugins/lua/clickhouse.lua @@ -18,21 +18,24 @@ local rspamd_logger = require 'rspamd_logger' local rspamd_http = require "rspamd_http" local rspamd_lua_utils = require "lua_util" local upstream_list = require "rspamd_upstream_list" +local lua_util = require "lua_util" +local lua_clickhouse = require "lua_clickhouse" +local fun = require "fun" + local N = "clickhouse" if confighelp then return end -local E = {} - -local rows = {} +local main_rows = {} local attachment_rows = {} local urls_rows = {} local emails_rows = {} -local specific_rows = {} +--local specific_rows = {} local asn_rows = {} local symbols_rows = {} +local custom_rows = {} local nrows = 0 local connect_prefix = 'http://' @@ -63,10 +66,20 @@ local settings = { use_https = false, use_gzip = true, allow_local = false, + user = nil, + password = nil, + no_ssl_verify = false, + custom_rules = {}, + retention = { + enable = false, + method = 'detach', + period_months = 3, + run_every = '7d', + } } local clickhouse_schema = { -rspamd = [[ +table = [[ CREATE TABLE IF NOT EXISTS ${table} ( Date Date, @@ -94,7 +107,7 @@ CREATE TABLE IF NOT EXISTS ${table} ) ENGINE = MergeTree(Date, (TS, From), 8192) ]], - attachments = [[ +attachments_table = [[ CREATE TABLE IF NOT EXISTS ${attachments_table} ( Date Date, Digest FixedString(32), @@ -105,7 +118,7 @@ CREATE TABLE IF NOT EXISTS ${attachments_table} ( ) ENGINE = MergeTree(Date, Digest, 8192) ]], - urls = [[ +urls_table = [[ CREATE TABLE IF NOT EXISTS ${urls_table} ( Date Date, Digest FixedString(32), @@ -114,7 +127,7 @@ CREATE TABLE IF NOT EXISTS ${urls_table} ( ) ENGINE = MergeTree(Date, Digest, 8192) ]], - emails = [[ +emails_table = [[ CREATE TABLE IF NOT EXISTS ${emails_table} ( Date Date, Digest FixedString(32), @@ -122,7 +135,7 @@ CREATE TABLE IF NOT EXISTS ${emails_table} ( ) ENGINE = MergeTree(Date, Digest, 8192) ]], - asn = [[ +asn_table = [[ CREATE TABLE IF NOT EXISTS ${asn_table} ( Date Date, Digest FixedString(32), @@ -132,7 +145,7 @@ CREATE TABLE IF NOT EXISTS ${asn_table} ( ) ENGINE = MergeTree(Date, Digest, 8192) ]], - symbols = [[ +symbols_table = [[ CREATE TABLE IF NOT EXISTS ${symbols_table} ( Date Date, Digest FixedString(32), @@ -168,7 +181,7 @@ local function clickhouse_main_row(tname) 'ListId', 'Digest' } - local elt = string.format('INSERT INTO %s (%s) VALUES ', + local elt = string.format('INSERT INTO %s (%s) ', tname, table.concat(fields, ',')) return elt @@ -183,7 +196,7 @@ local function clickhouse_attachments_row(tname) 'Attachments.Length', 'Attachments.Digest', } - local elt = string.format('INSERT INTO %s (%s) VALUES ', + local elt = string.format('INSERT INTO %s (%s) ', tname, table.concat(attachement_fields, ',')) return elt end @@ -195,7 +208,7 @@ local function clickhouse_urls_row(tname) 'Urls.Tld', 'Urls.Url', } - local elt = string.format('INSERT INTO %s (%s) VALUES ', + local elt = string.format('INSERT INTO %s (%s) ', tname, table.concat(urls_fields, ',')) return elt end @@ -206,7 +219,7 @@ local function clickhouse_emails_row(tname) 'Digest', 'Emails', } - local elt = string.format('INSERT INTO %s (%s) VALUES ', + local elt = string.format('INSERT INTO %s (%s) ', tname, table.concat(emails_fields, ',')) return elt end @@ -219,7 +232,7 @@ local function clickhouse_symbols_row(tname) 'Symbols.Scores', 'Symbols.Options', } - local elt = string.format('INSERT INTO %s (%s) VALUES ', + local elt = string.format('INSERT INTO %s (%s) ', tname, table.concat(symbols_fields, ',')) return elt end @@ -232,33 +245,13 @@ local function clickhouse_asn_row(tname) 'Country', 'IPNet', } - local elt = string.format('INSERT INTO %s (%s) VALUES ', + local elt = string.format('INSERT INTO %s (%s) ', tname, table.concat(asn_fields, ',')) return elt end -local function clickhouse_first_row() - table.insert(rows, clickhouse_main_row(settings['table'])) - if settings['attachments_table'] then - table.insert(attachment_rows, - clickhouse_attachments_row(settings['attachments_table'])) - end - if settings['urls_table'] then - table.insert(urls_rows, - clickhouse_urls_row(settings['urls_table'])) - end - if settings['emails_table'] then - table.insert(emails_rows, - clickhouse_emails_row(settings['emails_table'])) - end - if settings['asn_table'] then - table.insert(asn_rows, - clickhouse_asn_row(settings['asn_table'])) - end - if settings.enable_symbols and settings['symbols_table'] then - table.insert(symbols_rows, - clickhouse_symbols_row(settings['symbols_table'])) - end +local function today(ts) + return os.date('%Y-%m-%d', ts) end local function clickhouse_check_symbol(task, symbols, need_score) @@ -280,133 +273,74 @@ local function clickhouse_send_data(task) local upstream = settings.upstream:get_upstream_round_robin() local ip_addr = upstream:get_addr():to_string(true) - local function gen_http_cb(what, how_many) - return function (err_message, code, data, _) - if code ~= 200 or err_message then - if not err_message then err_message = data end - rspamd_logger.errx(task, "cannot send %s data to clickhouse server %s: %s", - what, ip_addr, err_message) - upstream:fail() - else - rspamd_logger.infox(task, "sent %s rows of %s to clickhouse server %s", - how_many - 1, what, ip_addr) - upstream:ok() - end + local function gen_success_cb(what, how_many) + return function (_, _) + rspamd_logger.infox(task, "sent %s rows of %s to clickhouse server %s", + how_many, what, ip_addr) + upstream:ok() end end - local body = table.concat(rows, ' ') - if not rspamd_http.request({ - task = task, - url = connect_prefix .. ip_addr, - body = body, - callback = gen_http_cb('generic data', #rows), - gzip = settings.use_gzip, - mime_type = 'text/plain', - timeout = settings['timeout'], - }) then - rspamd_logger.errx(task, "cannot send data to clickhouse server %s: cannot make request", - settings['server']) + local function gen_fail_cb(what, how_many) + return function (_, err) + rspamd_logger.errx(task, "cannot send %s rows of %s data to clickhouse server %s: %s", + how_many, what, ip_addr, err) + upstream:fail() + end end - if #attachment_rows > 1 then - body = table.concat(attachment_rows, ' ') - if not rspamd_http.request({ + local function send_data(what, tbl, query) + local ch_params = { task = task, - url = connect_prefix .. ip_addr, - body = body, - callback = gen_http_cb('attachments data', #attachment_rows), - mime_type = 'text/plain', - timeout = settings['timeout'], - }) then - rspamd_logger.errx(task, "cannot send attachments to clickhouse server %s: cannot make request", - settings['server']) + } + + local ret = lua_clickhouse.insert(upstream, settings, ch_params, + query, tbl, + gen_success_cb(what, #tbl), + gen_fail_cb(what, #tbl)) + if not ret then + rspamd_logger.errx(task, "cannot send %s rows of %s data to clickhouse server %s: %s", + #tbl, what, ip_addr, 'cannot make HTTP request') end end + + send_data('generic data', main_rows, + clickhouse_main_row(settings['table'])) + + + if #attachment_rows > 1 then + send_data('attachments data', attachment_rows, + clickhouse_attachments_row(settings.attachments_table)) + end + if #urls_rows > 1 then - body = table.concat(urls_rows, ' ') - if not rspamd_http.request({ - task = task, - url = connect_prefix .. ip_addr, - body = body, - callback = gen_http_cb('urls data', #urls_rows), - mime_type = 'text/plain', - timeout = settings['timeout'], - }) then - rspamd_logger.errx(task, "cannot send urls to clickhouse server %s: cannot make request", - settings['server']) - end + send_data('urls data', urls_rows, + clickhouse_urls_row(settings.urls_table)) end + if #emails_rows > 1 then - body = table.concat(emails_rows, ' ') - if not rspamd_http.request({ - task = task, - url = connect_prefix .. ip_addr, - body = body, - callback = gen_http_cb('emails data', #emails_rows), - mime_type = 'text/plain', - timeout = settings['timeout'], - }) then - rspamd_logger.errx(task, "cannot send emails to clickhouse server %s: cannot make request", - settings['server']) - end + send_data('emails data', emails_rows, + clickhouse_emails_row(settings.emails_table)) end + if #asn_rows > 1 then - body = table.concat(asn_rows, ' ') - if not rspamd_http.request({ - task = task, - url = connect_prefix .. ip_addr, - body = body, - callback = gen_http_cb('asn data', #asn_rows), - mime_type = 'text/plain', - timeout = settings['timeout'], - }) then - rspamd_logger.errx(task, "cannot send asn info to clickhouse server %s: cannot make request", - settings['server']) - end + send_data('asn data', asn_rows, + clickhouse_asn_row(settings.asn_table)) end if #symbols_rows > 1 then - body = table.concat(symbols_rows, ' ') - if not rspamd_http.request({ - task = task, - url = connect_prefix .. ip_addr, - body = body, - callback = gen_http_cb('symbols data', #symbols_rows), - mime_type = 'text/plain', - timeout = settings['timeout'], - }) then - rspamd_logger.errx(task, "cannot send symbols info to clickhouse server %s: cannot make request", - settings['server']) - end + send_data('symbols data', symbols_rows, + clickhouse_symbols_row(settings.symbols_table)) end - for k,specific in pairs(specific_rows) do - if #specific > 1 then - body = table.concat(specific, ' ') - if not rspamd_http.request({ - task = task, - url = connect_prefix .. ip_addr, - body = body, - callback = gen_http_cb('domain specific data ('..k..')', #specific), - mime_type = 'text/plain', - timeout = settings['timeout'], - }) then - rspamd_logger.errx(task, "cannot send data for domain %s to clickhouse server %s: cannot make request", - k, settings['server']) - end + for k,crows in pairs(custom_rows) do + if #crows > 1 then + send_data('custom data ('..k..')', settings.custom_rules[k].first_row(), + crows) end end end -local function clickhouse_quote(str) - if str then - return str:gsub('[\'\\]', '\\%1'):lower() - else - return '' - end -end - local function clickhouse_collect(task) if not settings.allow_local and rspamd_lua_utils.is_rspamc_or_controller(task) then return end @@ -553,16 +487,34 @@ local function clickhouse_collect(task) gmt = false }) - local elt = string.format("(today(),%d,'%s','%s','%s',%.2f,%d,%d,'%s','%s','%s','%s','%s','%s',%d,'%s','%s','%s','%s','%s','%s','%s')", - timestamp, - clickhouse_quote(from_domain), clickhouse_quote(mime_domain), ip_str, score, - nrcpts, task:get_size(), whitelist, bayes, fuzzy, fann, - dkim, dmarc, nurls, task:get_metric_action('default'), - clickhouse_quote(from_user), clickhouse_quote(mime_user), - clickhouse_quote(rcpt_user), clickhouse_quote(rcpt_domain), - clickhouse_quote(list_id), task:get_digest()) - table.insert(rows, elt) + local action = task:get_metric_action('default') + + table.insert(main_rows, { + today(timestamp), + timestamp, + from_domain, + mime_domain, + ip_str, + score, + nrcpts, + task:get_size(), + whitelist, + bayes, + fuzzy, + fann, + dkim, + dmarc, + nurls, + action, + from_user, + mime_user, + rcpt_user, + rcpt_domain, + list_id, + task:get_digest() + }) +--[[ TODO: has been broken if settings['from_map'] and dkim == 'allow' then -- Use dkim local das = task:get_symbol(settings['dkim_allow_symbols'][1]) @@ -570,16 +522,14 @@ local function clickhouse_collect(task) for _,dkim_domain in ipairs(das[1]['options']) do local specific = settings.from_map:get_key(dkim_domain) if specific then - if not specific_rows[specific] then - local first = clickhouse_main_row(specific) - specific_rows[specific] = {first} - end + specific_rows[specific] = {} table.insert(specific_rows[specific], elt) end end end end +--]] -- Attachments step local attachments_fnames = {} @@ -590,23 +540,24 @@ local function clickhouse_collect(task) local fname = part:get_filename() if fname then - table.insert(attachments_fnames, string.format("'%s'", clickhouse_quote(fname))) + table.insert(attachments_fnames, fname) local type, subtype = part:get_type() - table.insert(attachments_ctypes, string.format("'%s/%s'", - clickhouse_quote(type), clickhouse_quote(subtype))) - table.insert(attachments_lengths, string.format("%s", tostring(part:get_length()))) - table.insert(attachments_digests, string.format("'%s'", string.sub(part:get_digest(), 1, 16))) + table.insert(attachments_ctypes, string.format("%s/%s", + type, subtype)) + table.insert(attachments_lengths, part:get_length()) + table.insert(attachments_digests, string.sub(part:get_digest(), 1, 16)) end end if #attachments_fnames > 0 then - elt = string.format("(today(),'%s',[%s],[%s],[%s],[%s])", + table.insert(attachment_rows, { + today(timestamp), task:get_digest(), - table.concat(attachments_fnames, ','), - table.concat(attachments_ctypes, ','), - table.concat(attachments_lengths, ','), - table.concat(attachments_digests, ',')) - table.insert(attachment_rows, elt) + attachments_fnames, + attachments_ctypes, + attachments_lengths, + attachments_digests, + }) end -- Urls step @@ -614,40 +565,39 @@ local function clickhouse_collect(task) local urls_urls = {} if task:has_urls(false) then for _,u in ipairs(task:get_urls(false)) do - table.insert(urls_tlds, string.format("'%s'", clickhouse_quote(u:get_tld()))) + table.insert(urls_tlds, u:get_tld()) if settings['full_urls'] then - table.insert(urls_urls, string.format("'%s'", - clickhouse_quote(u:get_text()))) + table.insert(urls_urls, u:get_text()) else - table.insert(urls_urls, string.format("'%s'", - clickhouse_quote(u:get_host()))) + table.insert(urls_urls, u:get_host()) end end end if #urls_tlds > 0 then - elt = string.format("(today(),'%s',[%s],[%s])", + table.insert(urls_rows, { + today(timestamp), task:get_digest(), - table.concat(urls_tlds, ','), - table.concat(urls_urls, ',')) - table.insert(urls_rows, elt) + urls_tlds, + urls_urls + }) end -- Emails step local emails = {} if task:has_urls(true) then for _,u in ipairs(task:get_emails()) do - table.insert(emails, string.format("'%s'", clickhouse_quote( - string.format('%s@%s', u:get_user(), u:get_host()) - ))) + table.insert(emails, + string.format('%s@%s', u:get_user(), u:get_host())) end end if #emails > 0 then - elt = string.format("(today(),'%s',[%s])", - task:get_digest(), - table.concat(emails, ',')) - table.insert(emails_rows, elt) + table.insert(emails_rows, { + today(timestamp), + task:get_digest(), + emails, + }) end -- ASN information @@ -666,10 +616,13 @@ local function clickhouse_collect(task) if ret then ipnet = ret end - elt = string.format("(today(),'%s','%s','%s','%s')", + table.insert(asn_rows, { + today(timestamp), task:get_digest(), - clickhouse_quote(asn), clickhouse_quote(country), clickhouse_quote(ipnet)) - table.insert(asn_rows, elt) + asn, + country, + ipnet + }) end -- Symbols info @@ -680,47 +633,219 @@ local function clickhouse_collect(task) local options_tab = {} for _,s in ipairs(symbols) do - table.insert(syms_tab, string.format("'%s'", - clickhouse_quote(s.name or ''))) - table.insert(scores_tab, string.format('%.3f', s.score)) + table.insert(syms_tab, s.name or '') + table.insert(scores_tab, s.score) if s.options then - table.insert(options_tab, string.format("'%s'", - clickhouse_quote(table.concat(s.options, ',')))) + table.insert(options_tab, table.concat(s.options, ',')) else table.insert(options_tab, "''"); end end - elt = string.format("(today(),'%s',[%s],[%s],[%s])", - task:get_digest(), - table.concat(syms_tab, ','), - table.concat(scores_tab, ','), - table.concat(options_tab, ',')) + table.insert(symbols_rows, { + today(timestamp), + syms_tab, + scores_tab, + options_tab + }) + end - table.insert(symbols_rows, elt) + -- Custom data + for k,rule in pairs(settings.custom_rules) do + if not custom_rows[k] then custom_rows[k] = {} end + table.insert(custom_rows[k], rule.get_row(task)) end nrows = nrows + 1 + rspamd_logger.debugm(N, task, "add clickhouse row %s / %s", nrows, settings.limit) if nrows > settings['limit'] then clickhouse_send_data(task) nrows = 0 - rows = {} + main_rows = {} attachment_rows = {} urls_rows = {} emails_rows = {} - specific_rows = {} asn_rows = {} symbols_rows = {} - clickhouse_first_row() + custom_rows = {} + end +end + +local function do_remove_partition(ev_base, cfg, table_name, partition_id) + rspamd_logger.debugm(N, rspamd_config, "removing partition %s.%s", table_name, partition_id) + local upstream = settings.upstream:get_upstream_round_robin() + local remove_partition_sql = "ALTER TABLE ${table_name} ${remove_method} PARTITION ${partition_id}" + local remove_method = (settings.retention.method == 'drop') and 'DROP' or 'DETACH' + local sql_params = { + ['table_name'] = table_name, + ['remove_method'] = remove_method, + ['partition_id'] = partition_id + } + + local sql = rspamd_lua_utils.template(remove_partition_sql, sql_params) + + local ch_params = { + body = sql, + ev_base = ev_base, + cfg = cfg, + } + + local ret = lua_clickhouse.select(upstream, settings, ch_params, sql, + function(_, rows) + rspamd_logger.infox(rspamd_config, + 'detached partition %s:%s on server %s', table_name, partition_id, + settings['server']) + end, + function(_, err) + rspamd_logger.errx(rspamd_config, + "cannot detach partition %s:%s from server %s: %s", + table_name, partition_id, + settings['server'], err) + end) + + if not ret then + rspamd_logger.errx(rspamd_config, + "cannot detach partition %s:%s from server %s: cannot make request", + table_name, partition_id, + settings['server']) end end +--[[ + nil - file is not writable, do not perform removal + 0 - it's time to perform removal + <int> - how many seconds wait until next run +]] +local function get_last_removal_ago() + local ts_file = string.format('%s/%s', rspamd_paths['DBDIR'], 'clickhouse_retention_run') + local f, err = io.open(ts_file, 'r') + local write_file + local last_ts + + if err then + rspamd_logger.debugm(N, rspamd_config, 'Failed to open %s: %s', ts_file, err) + else + last_ts = tonumber(f:read('*number')) + f:close() + end + + local current_ts = os.time() + + if last_ts == nil or (last_ts + settings.retention.period) <= current_ts then + write_file, err = io.open(ts_file, 'w') + if err then + rspamd_logger.errx(rspamd_config, 'Failed to open %s, will not perform retention: %s', ts_file, err) + return nil + end + + local res + res, err = write_file:write(tostring(current_ts)) + if err or res == nil then + rspamd_logger.errx(rspamd_config, 'Failed to write %s, will not perform retention: %s', ts_file, err) + return nil + end + write_file:close() + return 0 + end + + return (last_ts + settings.retention.period) - current_ts +end + +local function clickhouse_remove_old_partitions(cfg, ev_base) + local last_time_ago = get_last_removal_ago() + if last_time_ago == nil then + rspamd_logger.errx(rspamd_config, "Failed to get last run time. Disabling retention") + return false + elseif last_time_ago ~= 0 then + return last_time_ago + end + + local upstream = settings.upstream:get_upstream_round_robin() + local partition_to_remove_sql = "SELECT distinct partition, table FROM system.parts WHERE table in ('${tables}') and max_date <= toDate(now() - interval ${month} month);" + + local table_names = {} + for table_name,_ in pairs(clickhouse_schema) do + table.insert(table_names, settings[table_name]) + end + local tables = table.concat(table_names, "', '") + local sql_params = { + tables = tables, + month = settings.retention.period_months, + } + local sql = rspamd_lua_utils.template(partition_to_remove_sql, sql_params) + + + local ch_params = { + ev_base = ev_base, + config = cfg, + } + local ret = lua_clickhouse.select(upstream, settings, ch_params, sql, + function(_, rows) + fun.each(function(row) + do_remove_partition(ev_base, cfg, row.table, row.partition) + end, rows) + end, + function(_, err) + rspamd_logger.errx(rspamd_config, + "cannot send data to clickhouse server %s: %s", + settings['server'], err) + end) + if not ret then + rspamd_logger.errx(rspamd_config, "cannot send data to clickhouse server %s: cannot make request", + settings['server']) + end + + -- settings.retention.period is added on initialisation, see below + return settings.retention.period +end + local opts = rspamd_config:get_all_opt('clickhouse') if opts then for k,v in pairs(opts) do - settings[k] = v + if k == 'custom_rules' then + if not v[1] then + v = {v} + end + + for i,rule in ipairs(v) do + if rule.schema and rule.first_row and rule.get_row then + local first_row, get_row + local loadstring = loadstring or load + local ret, res_or_err = pcall(loadstring(rule.first_row)) + + if not ret or type(res_or_err) ~= 'function' then + rspamd_logger.errx(rspamd_config, 'invalid first_row (%s) - must be a function', + res_or_err) + else + first_row = res_or_err + end + + ret, res_or_err = pcall(loadstring(rule.get_row)) + + if not ret or type(res_or_err) ~= 'function' then + rspamd_logger.errx(rspamd_config, 'invalid get_row (%s) - must be a function', + res_or_err) + else + get_row = res_or_err + end + + if first_row and get_row then + local name = rule.name or tostring(i) + settings.custom_rules[name] = { + schema = rule.schema, + first_row = first_row, + get_row = get_row, + } + end + else + rspamd_logger.errx(rspamd_config, 'custom rule has no required attributes: schema, first_row and get_row') + end + end + else + settings[k] = v + end end if not settings['server'] and not settings['servers'] then @@ -737,13 +862,12 @@ if opts then settings['server'] or settings['servers'], 8123) if not settings.upstream then - rspamd_logger.errx('cannot parse clickhouse address: %s', + rspamd_logger.errx(rspamd_config, 'cannot parse clickhouse address: %s', settings['server'] or settings['servers']) rspamd_lua_utils.disable_module(N, "config") return end - clickhouse_first_row() rspamd_config:register_symbol({ name = 'CLICKHOUSE_COLLECT', type = 'idempotent', @@ -784,6 +908,9 @@ if opts then callback = http_cb, mime_type = 'text/plain', timeout = settings['timeout'], + no_ssl_verify = settings.no_ssl_verify, + user = settings.user, + password = settings.password, }) then rspamd_logger.errx(rspamd_config, "cannot create table %s in clickhouse server %s: cannot make request", elt, ip_addr) @@ -793,6 +920,36 @@ if opts then for tab,sql in pairs(clickhouse_schema) do send_req(tab, rspamd_lua_utils.template(sql, settings)) end + + for k,rule in pairs(settings.custom_rules) do + if rule.schema then + send_req(k, rspamd_lua_utils.template(rule.schema, settings)) + end + end + end + + if settings.retention.enable and settings.retention.method ~= 'drop' and settings.retention.method ~= 'detach' then + rspamd_logger.errx(rspamd_config, "retention.method should be either 'drop' or 'detach' (now: %s). Disabling retention", + settings.retention.method) + settings.retention.enable = false + end + if settings.retention.enable and settings.retention.period_months < 1 or settings.retention.period_months > 1000 then + rspamd_logger.errx(rspamd_config, "please, set retention.period_months between 1 and 1000 (now: %s). Disabling retention", + settings.retention.period_months) + settings.retention.enable = false + end + local period = lua_util.parse_time_interval(settings.retention.run_every) + if settings.retention.enable and period == nil then + rspamd_logger.errx(rspamd_config, "invalid value for retention.run_every (%s). Disabling retention", + settings.retention.run_every) + settings.retention.enable = false + end + + if settings.retention.enable then + settings.retention.period = period + rspamd_logger.infox(rspamd_config, "retention will be performed each %s seconds for %s month with method %s", + period, settings.retention.period_months, settings.retention.method) + rspamd_config:add_periodic(ev_base, 0, clickhouse_remove_old_partitions, false) end end end) diff --git a/src/plugins/lua/dkim_signing.lua b/src/plugins/lua/dkim_signing.lua index 9b8f0a047..623705549 100644 --- a/src/plugins/lua/dkim_signing.lua +++ b/src/plugins/lua/dkim_signing.lua @@ -32,7 +32,9 @@ local settings = { allow_hdrfrom_mismatch_sign_networks = false, allow_hdrfrom_multiple = false, allow_username_mismatch = false, + allow_pubkey_mismatch = true, auth_only = true, + check_pubkey = false, domain = {}, path = string.format('%s/%s/%s', rspamd_paths['DBDIR'], 'dkim', '$domain.$selector.key'), sign_local = true, @@ -56,6 +58,41 @@ local function dkim_signing_cb(task) return end + local function do_sign() + if settings.check_pubkey then + local resolve_name = p.selector .. "._domainkey." .. p.domain + task:get_resolver():resolve_txt({ + task = task, + name = resolve_name, + callback = function(_, _, results, err) + task:inc_dns_req() + if not err and results and results[1] then + p.pubkey = results[1] + p.strict_pubkey_check = not settings.allow_pubkey_mismatch + elseif not settings.allow_pubkey_mismatch then + rspamd_logger.errx('public key for domain %s/%s is not found: %s, skip signing', + p.domain, p.selector, err) + return + else + rspamd_logger.infox('public key for domain %s/%s is not found: %s', + p.domain, p.selector, err) + end + + local sret, _ = sign_func(task, p) + if sret then + task:insert_result(settings.symbol, 1.0) + end + end, + forced = true + }) + else + local sret, _ = sign_func(task, p) + if sret then + task:insert_result(settings.symbol, 1.0) + end + end + end + if settings.use_redis then local function try_redis_key(selector) p.key = nil @@ -68,11 +105,8 @@ local function dkim_signing_cb(task) elseif type(data) ~= 'string' then rspamd_logger.debugm(N, task, "missing DKIM key for %s", rk) else - p.rawkey = data - local sret, _ = sign_func(task, p) - if sret then - task:insert_result(settings.symbol, 1.0) - end + p.rawkey = data + do_sign() end end local rret = rspamd_redis_make_request(task, @@ -117,12 +151,17 @@ local function dkim_signing_cb(task) else if (p.key and p.selector) then p.key = lutil.template(p.key, {domain = p.domain, selector = p.selector}) - if not rspamd_util.file_exists(p.key) then - rspamd_logger.debugm(N, task, 'file %s does not exists', p.key) + local exists,err = rspamd_util.file_exists(p.key) + if not exists then + if err and err == 'No such file or directory' then + rspamd_logger.debugm(N, task, 'cannot read key from %s: %s', p.key, err) + else + rspamd_logger.warnx(N, task, 'cannot read key from %s: %s', p.key, err) + end return false end - local sret, _ = sign_func(task, p) - return sret + + do_sign() else rspamd_logger.infox(task, 'key path or dkim selector unconfigured; no signing') return false diff --git a/src/plugins/lua/dmarc.lua b/src/plugins/lua/dmarc.lua index 5c24bff53..a7f53b6b2 100644 --- a/src/plugins/lua/dmarc.lua +++ b/src/plugins/lua/dmarc.lua @@ -37,7 +37,7 @@ local no_reporting_domains local statefile = string.format('%s/%s', rspamd_paths['DBDIR'], 'dmarc_reports_last_sent') local VAR_NAME = 'dmarc_reports_last_sent' local INTERVAL = 86400 -local pool = mempool.create() +local pool local report_settings = { helo = 'rspamd', @@ -198,6 +198,8 @@ local function dmarc_callback(task) local function maybe_force_action(disposition) local force_action = dmarc_actions[disposition] if force_action then + -- Don't do anything if pre-result has been already set + if task:has_pre_result() then return end task:set_pre_result(force_action, 'Action set by DMARC') end end @@ -608,6 +610,7 @@ if opts['reporting'] == true then rspamd_config:add_on_load(function(cfg, ev_base, worker) if not worker:is_primary_controller() then return end local rresolver = rspamd_resolver.init(ev_base, rspamd_config) + pool = mempool.create() rspamd_config:register_finish_script(function () local stamp = pool:get_variable(VAR_NAME, 'double') if not stamp then @@ -621,6 +624,7 @@ if opts['reporting'] == true then end assert(f:write(pool:get_variable(VAR_NAME, 'double'))) assert(f:close()) + pool:destroy() end) local get_reporting_domain, reporting_domain, report_start, report_end, report_id, want_period, report_key local reporting_addr = {} diff --git a/src/plugins/lua/dynamic_conf.lua b/src/plugins/lua/dynamic_conf.lua index 29f63e90c..ffe1f7e9c 100644 --- a/src/plugins/lua/dynamic_conf.lua +++ b/src/plugins/lua/dynamic_conf.lua @@ -319,10 +319,15 @@ local function add_dynamic_action(_, act, score) return add end -if redis_params then - rspamd_plugins["dynamic_conf"] = { - add_symbol = add_dynamic_symbol, - add_action = add_dynamic_action, - } - lua_util.disable_module(N, "redis") -end +if section then + if redis_params then + rspamd_plugins["dynamic_conf"] = { + add_symbol = add_dynamic_symbol, + add_action = add_dynamic_action, + } + else + lua_util.disable_module(N, "redis") + end +else + lua_util.disable_module(N, "config") +end
\ No newline at end of file diff --git a/src/plugins/lua/elastic.lua b/src/plugins/lua/elastic.lua index 3a80256d2..454ec7ef0 100644 --- a/src/plugins/lua/elastic.lua +++ b/src/plugins/lua/elastic.lua @@ -43,11 +43,15 @@ local settings = { kibana_file = rspamd_paths['PLUGINSDIR'] ..'/elastic/kibana.json', key_prefix = 'elastic-', expire = 3600, + timeout = 5.0, failover = false, import_kibana = false, use_https = false, use_gzip = true, allow_local = false, + user = nil, + password = nil, + no_ssl_verify = false, } local function read_file(path) @@ -74,18 +78,20 @@ local function elastic_send_data(task) local push_url = connect_prefix .. ip_addr .. '/'..es_index..'/_bulk' local bulk_json = table.concat(tbl, "\n") - local function http_index_data_callback(_, code, body, _) + local function http_index_data_callback(err, code, body, _) -- todo error handling we may store the rows it into redis and send it again late rspamd_logger.debugm(N, task, "After create data %1", body) if code ~= 200 then + rspamd_logger.infox(task, "cannot push data to elastic backend (%s): %s (%s)", + push_url, err, code) if settings['failover'] then local h = hash.create() h:update(bulk_json) local key = settings['key_prefix'] ..es_index..":".. h:base32():sub(1, 20) local data = util.zstd_compress(bulk_json) - local function redis_set_cb(err) - if err ~=nil then - rspamd_logger.errx(task, 'redis_set_cb received error: %1', err) + local function redis_set_cb(rerr) + if rerr ~=nil then + rspamd_logger.errx(task, 'redis_set_cb received error: %1', rerr) end end rspamd_redis.make_request(task, @@ -108,7 +114,11 @@ local function elastic_send_data(task) task = task, method = 'post', gzip = settings.use_gzip, - callback = http_index_data_callback + no_ssl_verify = settings.no_ssl_verify, + user = settings.user, + password = settings.password, + callback = http_index_data_callback, + timeout = settings.timeout, }) end @@ -218,8 +228,8 @@ local function check_elastic_server(cfg, ev_base, _) local ip_addr = upstream:get_addr():to_string(true) local plugins_url = connect_prefix .. ip_addr .. '/_nodes/plugins' - local function http_callback(_, err, body, _) - if err == 200 then + local function http_callback(err, code, body, _) + if code == 200 then local parser = ucl.parser() local res,ucl_err = parser:parse_string(body) if not res then @@ -244,7 +254,8 @@ local function check_elastic_server(cfg, ev_base, _) end end else - rspamd_logger.errx('cannot get plugins from %s: %s (%s)', plugins_url, err, body) + rspamd_logger.errx('cannot get plugins from %s: %s(%s) (%s)', plugins_url, + err, code, body) enabled = false end end @@ -253,7 +264,11 @@ local function check_elastic_server(cfg, ev_base, _) ev_base = ev_base, config = cfg, method = 'get', - callback = http_callback + callback = http_callback, + no_ssl_verify = settings.no_ssl_verify, + user = settings.user, + password = settings.password, + timeout = settings.timeout, }) end @@ -270,10 +285,10 @@ local function initial_setup(cfg, ev_base, worker) local kibana_mappings = read_file(settings['kibana_file']) if kibana_mappings then local parser = ucl.parser() - local res,err = parser:parse_string(kibana_mappings) + local res,parser_err = parser:parse_string(kibana_mappings) if not res then rspamd_logger.infox(rspamd_config, 'kibana template cannot be parsed: %s', - err) + parser_err) enabled = false return @@ -288,10 +303,10 @@ local function initial_setup(cfg, ev_base, worker) table.insert(tbl, '') -- For last \n local kibana_url = connect_prefix .. ip_addr ..'/.kibana/_bulk' - local function kibana_template_callback(_, code, body, _) + local function kibana_template_callback(err, code, body, _) if code ~= 200 then - rspamd_logger.errx('cannot put template to %s: %s (%s)', kibana_url, - code, body) + rspamd_logger.errx('cannot put template to %s: %s(%s) (%s)', kibana_url, + err, code, body) enabled = false else rspamd_logger.debugm(N, 'pushed kibana template: %s', body) @@ -308,7 +323,11 @@ local function initial_setup(cfg, ev_base, worker) body = table.concat(tbl, "\n"), method = 'post', gzip = settings.use_gzip, - callback = kibana_template_callback + callback = kibana_template_callback, + no_ssl_verify = settings.no_ssl_verify, + user = settings.user, + password = settings.password, + timeout = settings.timeout, }) else rspamd_logger.infox(rspamd_config, 'kibana template file %s not found', settings['kibana_file']) @@ -319,9 +338,10 @@ local function initial_setup(cfg, ev_base, worker) if enabled then -- create ingest pipeline local geoip_url = connect_prefix .. ip_addr ..'/_ingest/pipeline/rspamd-geoip' - local function geoip_cb(_, code, body, _) + local function geoip_cb(err, code, body, _) if code ~= 200 then - rspamd_logger.errx('cannot get data from %s: %s (%s)', geoip_url, code, body) + rspamd_logger.errx('cannot get data from %s: %s(%s) (%s)', + geoip_url, err, code, body) enabled = false end end @@ -347,12 +367,17 @@ local function initial_setup(cfg, ev_base, worker) gzip = settings.use_gzip, body = ucl.to_format(template, 'json-compact'), method = 'put', + no_ssl_verify = settings.no_ssl_verify, + user = settings.user, + password = settings.password, + timeout = settings.timeout, }) -- create template mappings if not exist local template_url = connect_prefix .. ip_addr ..'/_template/rspamd' - local function http_template_put_callback(_, code, body, _) + local function http_template_put_callback(err, code, body, _) if code ~= 200 then - rspamd_logger.errx('cannot put template to %s: %s (%s)', template_url, code, body) + rspamd_logger.errx('cannot put template to %s: %s(%s) (%s)', + template_url, err, code, body) enabled = false else rspamd_logger.debugm(N, 'pushed rspamd template: %s', body) @@ -372,6 +397,10 @@ local function initial_setup(cfg, ev_base, worker) }, gzip = settings.use_gzip, callback = http_template_put_callback, + no_ssl_verify = settings.no_ssl_verify, + user = settings.user, + password = settings.password, + timeout = settings.timeout, }) else push_kibana_template() @@ -383,7 +412,11 @@ local function initial_setup(cfg, ev_base, worker) ev_base = ev_base, config = cfg, method = 'head', - callback = http_template_exist_callback + callback = http_template_exist_callback, + no_ssl_verify = settings.no_ssl_verify, + user = settings.user, + password = settings.password, + timeout = settings.timeout, }) end diff --git a/src/plugins/lua/greylist.lua b/src/plugins/lua/greylist.lua index 8de4f69c8..0bf74bc74 100644 --- a/src/plugins/lua/greylist.lua +++ b/src/plugins/lua/greylist.lua @@ -249,6 +249,9 @@ local function greylist_set(task) local action = task:get_metric_action('default') local ip = task:get_ip() + -- Don't do anything if pre-result has been already set + if task:has_pre_result() then return end + if settings.greylist_min_score then local score = task:get_metric_score('default')[1] if score < settings.greylist_min_score then diff --git a/src/plugins/lua/maillist.lua b/src/plugins/lua/maillist.lua index 62ecc0341..2ec0c4255 100644 --- a/src/plugins/lua/maillist.lua +++ b/src/plugins/lua/maillist.lua @@ -221,7 +221,7 @@ end local function check_ml_cgp(task) local header = task:get_header('X-Listserver') - if not header or header ~= 'CommuniGate Pro LIST' then + if not header or string.sub(header, 0, 20) ~= 'CommuniGate Pro LIST' then return false end diff --git a/src/plugins/lua/metric_exporter.lua b/src/plugins/lua/metric_exporter.lua index ee435b23b..b4b7756b4 100644 --- a/src/plugins/lua/metric_exporter.lua +++ b/src/plugins/lua/metric_exporter.lua @@ -26,7 +26,7 @@ local util = require "rspamd_util" local tcp = require "rspamd_tcp" local lua_util = require "lua_util" -local pool = mempool.create() +local pool local settings = { interval = 120, timeout = 15, @@ -125,7 +125,6 @@ local function graphite_push(kwargs) tcp.request({ ev_base = kwargs['ev_base'], config = rspamd_config, - pool = pool, host = settings['host'], port = settings['port'], timeout = settings['timeout'], @@ -176,6 +175,7 @@ rspamd_config:add_on_load(function (_, ev_base, worker) -- Exit unless we're the first 'controller' worker if not worker:is_primary_controller() then return end -- Persist mempool variable to statefile on shutdown + pool = mempool.create() rspamd_config:register_finish_script(function () local stamp = pool:get_variable(VAR_NAME, 'double') if not stamp then @@ -191,6 +191,7 @@ rspamd_config:add_on_load(function (_, ev_base, worker) f:write(pool:get_variable(VAR_NAME, 'double')) f:close() end + pool:destroy() end) -- Push metrics to backend local function push_metrics(time) diff --git a/src/plugins/lua/multimap.lua b/src/plugins/lua/multimap.lua index 7e83988c6..742f96666 100644 --- a/src/plugins/lua/multimap.lua +++ b/src/plugins/lua/multimap.lua @@ -93,6 +93,9 @@ local function apply_hostname_filter(task, filter, hostname, r) if filter == 'tld' then local tld = util.get_tld(hostname) return tld + elseif filter == 'top' then + local tld = util.get_tld(hostname) + return tld:match('[^.]*$') or tld else if not r['re_filter'] then local pat = string.match(filter, 'tld:regexp:(.+)') @@ -123,6 +126,9 @@ local function apply_url_filter(task, filter, url, r) if filter == 'tld' then return url:get_tld() + elseif filter == 'top' then + local tld = url:get_tld() + return tld:match('[^.]*$') or tld elseif filter == 'full' then return url:get_text() elseif filter == 'is_phished' then @@ -407,6 +413,7 @@ local function multimap_callback(task, rule) table.insert(srch, nip) end end + table.insert(srch, 1, r['redis_key']) ret = rspamd_redis_make_request(task, redis_params, -- connect params @@ -753,6 +760,7 @@ local function multimap_callback(task, rule) url = function() if task:has_urls() then local msg_urls = task:get_urls() + for _,url in ipairs(msg_urls) do match_url(rule, url) end @@ -889,8 +897,8 @@ local function add_multimap_rule(key, newrule) else if type(newrule['map']) == 'string' then local map = urls[newrule['map']] - if map and map['type'] == newrule['type'] - and map['regexp'] == newrule['regexp'] then + if map and map['regexp'] == newrule['regexp'] and + map['glob'] == newrule['glob'] then if newrule['type'] == 'ip' then newrule['radix'] = map['map'] else @@ -1089,6 +1097,8 @@ if opts and type(opts) == 'table' then if not rule then rspamd_logger.errx(rspamd_config, 'cannot add rule: "'..k..'"') else + rspamd_logger.infox(rspamd_config, 'added multimap rule: %s (%s)', + k, rule.type) table.insert(rules, rule) end end @@ -1114,20 +1124,11 @@ if opts and type(opts) == 'table' then end if rule['score'] then -- Register metric symbol - local description = 'multimap symbol' - local group = N - if rule['description'] then - description = rule['description'] - end - if rule['group'] then - group = rule['group'] - end - rspamd_config:set_metric_symbol({ - name = rule['symbol'], - score = rule['score'], - description = description, - group = group - }) + rule.name = rule.symbol + rule.description = rule.description or 'multimap symbol' + rule.group = rule.group or N + + rspamd_config:set_metric_symbol(rule) end end, fun.filter(function(r) return not r['prefilter'] end, rules)) diff --git a/src/plugins/lua/phishing.lua b/src/plugins/lua/phishing.lua index 69aec219f..cc6a259bd 100644 --- a/src/plugins/lua/phishing.lua +++ b/src/plugins/lua/phishing.lua @@ -23,19 +23,23 @@ end -- local N = 'phishing' local symbol = 'PHISHED_URL' +local generic_service_symbol = 'PHISHED_GENERIC_SERVICE' local openphish_symbol = 'PHISHED_OPENPHISH' local phishtank_symbol = 'PHISHED_PHISHTANK' +local generic_service_name = 'generic service' local domains = nil local strict_domains = {} local redirector_domains = {} +local generic_service_map = nil local openphish_map = 'https://www.openphish.com/feed.txt' -local phishtank_map = 'http://data.phishtank.com/data/online-valid.json' +local phishtank_suffix = 'phishtank.rspamd.com' -- Not enabled by default as their feed is quite large local openphish_premium = false +local phishtank_enabled = false +local generic_service_hash local openphish_hash -local phishtank_hash +local generic_service_data = {} local openphish_data = {} -local phishtank_data = {} local rspamd_logger = require "rspamd_logger" local util = require "rspamd_util" local opts = rspamd_config:get_all_opt(N) @@ -120,16 +124,59 @@ local function phishing_cb(task) end end + local function check_phishing_dns(dns_suffix, url, phish_symbol) + local function compose_dns_query(elts) + local cr = require "rspamd_cryptobox_hash" + local h = cr.create() + for _,elt in ipairs(elts) do h:update(elt) end + return string.format("%s.%s", h:base32():sub(1, 32), dns_suffix) + end + local r = task:get_resolver() + local host = url:get_host() + local path = url:get_path() + local query = url:get_query() + + if host and path then + local function host_host_path_cb(_, _, results, err) + if not err and results then + task:insert_result(phish_symbol, 0.3, results) + end + end + r:resolve_a({ + task = task, + name = compose_dns_query({host, path}), + callback = host_host_path_cb}) + + + if query then + local function host_host_path_query_cb(_, _, results, err) + if not err and results then + task:insert_result(phish_symbol, 1.0, results) + end + end + r:resolve_a({ + task = task, + name = compose_dns_query({host, path, query}), + callback = host_host_path_query_cb}) + end + + end + end + local urls = task:get_urls() if urls then for _,url in ipairs(urls) do + if generic_service_hash then + check_phishing_map(generic_service_data, url, generic_service_symbol) + end + if openphish_hash then check_phishing_map(openphish_data, url, openphish_symbol) end - if phishtank_hash then - check_phishing_map(phishtank_data, url, phishtank_symbol) + if phishtank_enabled then + check_phishing_dns(phishtank_suffix, url, phishtank_symbol) end if url:is_phished() and not url:is_redirected() then @@ -228,27 +275,40 @@ local function phishing_map(mapname, phishmap, id) else xd[1] = opts[mapname] end + + local found_maps = {} + for _,d in ipairs(xd) do local s = string.find(d, ':[^:]+$') if s then local sym = string.sub(d, s + 1, -1) local map = string.sub(d, 1, s - 1) - rspamd_config:register_virtual_symbol(sym, 1, id) - local rmap = rspamd_config:add_map ({ - type = 'set', - url = map, - description = 'Phishing ' .. mapname .. ' map', - }) - if rmap then - local rule = {symbol = sym, map = rmap} - table.insert(phishmap, rule) + + if found_maps[sym] then + table.insert(found_maps[sym], map) else - rspamd_logger.infox(rspamd_config, 'cannot add map: ' .. map .. ' for symbol: ' .. sym) + found_maps[sym] = {map} end else rspamd_logger.infox(rspamd_config, mapname .. ' option must be in format <map>:<symbol>') end end + + for sym,urls in pairs(found_maps) do + local rmap = rspamd_config:add_map ({ + type = 'set', + url = urls, + description = 'Phishing ' .. mapname .. ' map', + }) + if rmap then + rspamd_config:register_virtual_symbol(sym, 1, id) + local rule = {symbol = sym, map = rmap} + table.insert(phishmap, rule) + else + rspamd_logger.infox(rspamd_config, 'cannot add map: %s for symbol: %s', + table.concat(urls, ";"), sym) + end + end end end @@ -287,6 +347,26 @@ local function insert_url_from_string(pool, tbl, str, data) return false end +local function generic_service_plain_cb(string) + local nelts = 0 + local new_data = {} + local rspamd_mempool = require "rspamd_mempool" + local pool = rspamd_mempool.create() + + local function generic_service_elt_parser(cap) + if insert_url_from_string(pool, new_data, cap, nil) then + nelts = nelts + 1 + end + end + + rspamd_str_split_fun(string, '\n', generic_service_elt_parser) + + generic_service_data = new_data + rspamd_logger.infox(generic_service_hash, "parsed %s elements from %s feed", + nelts, generic_service_name) + pool:destroy() +end + local function openphish_json_cb(string) local ucl = require "ucl" local rspamd_mempool = require "rspamd_mempool" @@ -346,42 +426,6 @@ local function openphish_plain_cb(string) pool:destroy() end -local function phishtank_json_cb(string) - local ucl = require "ucl" - local nelts = 0 - local new_data = {} - local valid = true - local parser = ucl.parser() - local res,err = parser:parse_string(string) - local rspamd_mempool = require "rspamd_mempool" - local pool = rspamd_mempool.create() - - if not res then - valid = false - rspamd_logger.warnx(phishtank_hash, 'cannot parse openphish map: ' .. err) - else - local obj = parser:get_object() - - for _,elt in ipairs(obj) do - if elt['url'] then - if insert_url_from_string(pool, new_data, elt['url'], - elt['phish_detail_url']) then - nelts = nelts + 1 - end - end - end - end - - if valid then - phishtank_data = new_data - rspamd_logger.infox(phishtank_hash, "parsed %s elements from phishtank feed", - nelts) - end - - - pool:destroy() -end - if opts then local id if opts['symbol'] then @@ -392,6 +436,28 @@ if opts then callback = phishing_cb }) + if opts['generic_service_symbol'] then + generic_service_symbol = opts['generic_service_symbol'] + end + if opts['generic_service_map'] then + generic_service_map = opts['generic_service_map'] + end + if opts['generic_service_url'] then + generic_service_map = opts['generic_service_url'] + end + if opts['generic_service_name'] then + generic_service_name = opts['generic_service_name'] + end + + if opts['generic_service_enabled'] then + generic_service_hash = rspamd_config:add_map({ + type = 'callback', + url = generic_service_map, + callback = generic_service_plain_cb, + description = 'Generic feed' + }) + end + if opts['openphish_map'] then openphish_map = opts['openphish_map'] end @@ -421,25 +487,22 @@ if opts then end end - if opts['phishtank_map'] then - phishtank_map = opts['phishtank_map'] - end - if opts['phishtank_url'] then - phishtank_map = opts['phishtank_url'] - end - if opts['phishtank_enabled'] then - phishtank_hash = rspamd_config:add_map({ - type = 'callback', - url = phishtank_map, - callback = phishtank_json_cb, - description = 'Phishtank feed (see https://www.phishtank.com for details)' - }) + phishtank_enabled = true + if opts['phishtank_suffix'] then + phishtank_suffix = opts['phishtank_suffix'] + end end rspamd_config:register_symbol({ type = 'virtual', parent = id, + name = generic_service_symbol, + }) + + rspamd_config:register_symbol({ + type = 'virtual', + parent = id, name = openphish_symbol, }) diff --git a/src/plugins/lua/ratelimit.lua b/src/plugins/lua/ratelimit.lua index 1d22f8838..839ec5c6c 100644 --- a/src/plugins/lua/ratelimit.lua +++ b/src/plugins/lua/ratelimit.lua @@ -79,17 +79,18 @@ local bucket_check_script = [[ burst = burst - leaked redis.call('HINCRBYFLOAT', KEYS[1], 'b', -(leaked)) end - dynb = tonumber(redis.call('HGET', KEYS[1], 'db')) / 10000.0 - - if burst * dynb > tonumber(KEYS[4]) then - return {1, burst, dynr, dynb} - end else - burst = 0 - redis.call('HSET', KEYS[1], 'b', '0') + burst = 0 + redis.call('HSET', KEYS[1], 'b', '0') end - return {0, burst, tostring(dynr), tostring(dynb)} + dynb = tonumber(redis.call('HGET', KEYS[1], 'db')) / 10000.0 + + if (burst + 1) * dynb > tonumber(KEYS[4]) then + return {1, tostring(burst), tostring(dynr), tostring(dynb)} + end + + return {0, tostring(burst), tostring(dynr), tostring(dynb)} ]] local bucket_check_id @@ -138,7 +139,7 @@ local bucket_update_script = [[ redis.call('HSET', KEYS[1], 'l', KEYS[2]) redis.call('EXPIRE', KEYS[1], KEYS[7]) - return {burst, tostring(dr), tostring(db)} + return {tostring(burst), tostring(dr), tostring(db)} ]] local bucket_update_id @@ -226,6 +227,50 @@ local function parse_string_limit(lim, no_error) return nil end +local function parse_limit(name, data) + local buckets = {} + if type(data) == 'table' then + -- 3 cases here: + -- * old limit in format [burst, rate] + -- * vector of strings in Andrew's string format + -- * proper bucket table + if #data == 2 and tonumber(data[1]) and tonumber(data[2]) then + -- Old style ratelimit + rspamd_logger.warnx(rspamd_config, 'old style ratelimit for %s', name) + if tonumber(data[1]) > 0 and tonumber(data[2]) > 0 then + table.insert(buckets, { + burst = data[1], + rate = data[2] + }) + elseif data[1] ~= 0 then + rspamd_logger.warnx(rspamd_config, 'invalid numbers for %s', name) + else + rspamd_logger.infox(rspamd_config, 'disable limit %s, burst is zero', name) + end + else + -- Recursively map parse_limit and flatten the list + fun.each(function(l) + -- Flatten list + for _,b in ipairs(l) do table.insert(buckets, b) end + end, fun.map(function(d) return parse_limit(d, name) end, data)) + end + elseif type(data) == 'string' then + local rep_rate, burst = parse_string_limit(data) + + if rep_rate and burst then + table.insert(buckets, { + burst = burst, + rate = 1.0 / rep_rate -- reciprocal + }) + end + end + + -- Filter valid + return fun.totable(fun.filter(function(val) + return type(val.burst) == 'number' and type(val.rate) == 'number' + end, buckets)) +end + --- Check whether this addr is bounce local function check_bounce(from) return fun.any(function(b) return b == from end, settings.bounce_senders) @@ -292,7 +337,7 @@ local keywords = { } local function gen_rate_key(task, rtype, bucket) - local key_t = {tostring(lua_util.round(100000.0 / bucket[1]))} + local key_t = {tostring(lua_util.round(100000.0 / bucket.burst))} local key_keywords = lua_util.str_split(rtype, '_') local have_user = false @@ -315,6 +360,46 @@ local function gen_rate_key(task, rtype, bucket) return table.concat(key_t, ":") end +local function make_prefix(redis_key, name, bucket) + local hash_len = 24 + if hash_len > #redis_key then hash_len = #redis_key end + local hash = settings.prefix .. + string.sub(rspamd_hash.create(redis_key):base32(), 1, hash_len) + -- Fill defaults + if not bucket.spam_factor_rate then + bucket.spam_factor_rate = settings.spam_factor_rate + end + if not bucket.ham_factor_rate then + bucket.ham_factor_rate = settings.ham_factor_rate + end + if not bucket.spam_factor_burst then + bucket.spam_factor_burst = settings.spam_factor_burst + end + if not bucket.ham_factor_burst then + bucket.ham_factor_burst = settings.ham_factor_burst + end + + return { + bucket = bucket, + name = name, + hash = hash + } +end + +local function limit_to_prefixes(task, k, v, prefixes) + local n = 0 + for _,bucket in ipairs(v) do + local prefix = gen_rate_key(task, k, bucket) + + if prefix then + prefixes[prefix] = make_prefix(prefix, k, bucket) + n = n + 1 + end + end + + return n +end + local function ratelimit_cb(task) if not settings.allow_local and rspamd_lua_utils.is_rspamc_or_controller(task) then return end @@ -354,21 +439,21 @@ local function ratelimit_cb(task) local nprefixes = 0 for k,v in pairs(settings.limits) do - for _,bucket in ipairs(v) do - local prefix = gen_rate_key(task, k, bucket) - - if prefix then - local hash_len = 24 - if hash_len > #prefix then hash_len = #prefix end - local hash = settings.prefix .. - string.sub(rspamd_hash.create(prefix):base32(), 1, hash_len) - prefixes[prefix] = { - bucket = bucket, - name = k, - hash = hash - } - nprefixes = nprefixes + 1 + nprefixes = nprefixes + limit_to_prefixes(task, k, v, prefixes) + end + + for k, hdl in pairs(settings.custom_keywords or E) do + local ret, redis_key, bd = pcall(hdl, task) + + if ret then + local bucket = parse_limit(k, bd) + if bucket[1] then + prefixes[redis_key] = make_prefix(redis_key, k, bucket[1]) end + nprefixes = nprefixes + 1 + else + rspamd_logger.errx(task, 'cannot call handler for %s: %s', + k, redis_key) end end @@ -381,22 +466,30 @@ local function ratelimit_cb(task) if settings.symbol then task:insert_result(settings.symbol, 0.0, lim_name .. "(" .. prefix .. ")") rspamd_logger.infox(task, - 'set_symbol_only: ratelimit "%s(%s)" exceeded, (%s / %s): %s (%s:%s dyn)', - lim_name, prefix, bucket[2], bucket[1], data[2], data[3], data[4]) + 'set_symbol_only: ratelimit "%s(%s)" exceeded, (%s / %s): %s (%s:%s dyn)', + lim_name, prefix, + bucket.burst, bucket.rate, + data[2], data[3], data[4]) return -- set INFO symbol and soft reject elseif settings.info_symbol then - task:insert_result(settings.info_symbol, 1.0, lim_name .. "(" .. prefix .. ")") + task:insert_result(settings.info_symbol, 1.0, + lim_name .. "(" .. prefix .. ")") end rspamd_logger.infox(task, - 'ratelimit "%s(%s)" exceeded, (%s / %s): %s (%s:%s dyn)', - lim_name, prefix, bucket[2], bucket[1], data[2], data[3], data[4]) + 'ratelimit "%s(%s)" exceeded, (%s / %s): %s (%s:%s dyn)', + lim_name, prefix, + bucket.burst, bucket.rate, + data[2], data[3], data[4]) task:set_pre_result('soft reject', message_func(task, lim_name, prefix, bucket)) end end end + -- Don't do anything if pre-result has been already set + if task:has_pre_result() then return end + if nprefixes > 0 then -- Save prefixes to the cache to allow update task:cache_set('ratelimit_prefixes', prefixes) @@ -406,13 +499,13 @@ local function ratelimit_cb(task) for pr,value in pairs(prefixes) do local bucket = value.bucket - local rate = (1.0 / bucket[1]) / 1000.0 -- Leak rate in messages/ms + local rate = (bucket.rate) / 1000.0 -- Leak rate in messages/ms rspamd_logger.debugm(N, task, "check limit %s:%s -> %s (%s/%s)", - value.name, pr, value.hash, bucket[2], bucket[1]) + value.name, pr, value.hash, bucket.burst, bucket.rate) lua_redis.exec_redis_script(bucket_check_id, {key = value.hash, task = task, is_write = true}, gen_check_cb(pr, bucket, value.name), - {value.hash, tostring(now), tostring(rate), tostring(bucket[2]), + {value.hash, tostring(now), tostring(rate), tostring(bucket.burst), tostring(settings.expire)}) end end @@ -422,24 +515,13 @@ local function ratelimit_update_cb(task) local prefixes = task:cache_get('ratelimit_prefixes') if prefixes then - local action = task:get_metric_action() - local is_spam = true - - if action == 'soft reject' then + if task:has_pre_result() then -- Already rate limited/greylisted, do nothing - rspamd_logger.debugm(N, task, 'already soft rejected, do not update') + rspamd_logger.debugm(N, task, 'pre-action has been set, do not update') return - elseif action == 'no action' then - is_spam = false end - local mult_burst = settings.ham_factor_burst - local mult_rate = settings.ham_factor_burst - - if is_spam then - mult_burst = settings.spam_factor_burst - mult_rate = settings.spam_factor_rate - end + local is_spam = not (task:get_metric_action() == 'no action') -- Update each bucket for k, v in pairs(prefixes) do @@ -450,12 +532,21 @@ local function ratelimit_update_cb(task) k, err) else rspamd_logger.debugm(N, task, - "updated limit %s:%s -> %s (%s/%s), burst: %s, dyn_rate: %s, dyn_burst: %s", - v.name, k, v.hash, bucket[2], bucket[1], data[1], data[2], data[3]) + "updated limit %s:%s -> %s (%s/%s), burst: %s, dyn_rate: %s, dyn_burst: %s", + v.name, k, v.hash, + bucket.burst, bucket.rate, + data[1], data[2], data[3]) end end local now = rspamd_util.get_time() now = lua_util.round(now * 1000.0) -- Get milliseconds + local mult_burst = bucket.ham_factor_burst or 1.0 + local mult_rate = bucket.ham_factor_burst or 1.0 + + if is_spam then + mult_burst = bucket.spam_factor_burst or 1.0 + mult_rate = bucket.spam_factor_rate or 1.0 + end lua_redis.exec_redis_script(bucket_update_id, {key = v.hash, task = task, is_write = true}, @@ -479,31 +570,10 @@ if opts then if opts['rates'] and type(opts['rates']) == 'table' then -- new way of setting limits fun.each(function(t, lim) - if type(lim) == 'table' then - settings.limits[t] = {} - if #lim == 2 and tonumber(lim[1]) and tonumber(lim[2]) then - -- Old style ratelimit - rspamd_logger.warnx(rspamd_config, 'old style ratelimit for %s', t) - if tonumber(lim[1]) > 0 and tonumber(lim[2]) > 0 then - table.insert(settings.limits[t], {1.0/lim[2], lim[1]}) - elseif lim[1] ~= 0 then - rspamd_logger.warnx(rspamd_config, 'invalid numbers for %s', t) - else - rspamd_logger.infox(rspamd_config, 'disable limit %s, burst is zero', t) - end - else - fun.each(function(l) - local plim, size = parse_string_limit(l) - if plim then - table.insert(settings.limits[t], {plim, size}) - end - end, lim) - end - elseif type(lim) == 'string' then - local plim, size = parse_string_limit(lim) - if plim then - settings.limits[t] = { {plim, size} } - end + local buckets = parse_limit(t, lim) + + if buckets and #buckets > 0 then + settings.limits[t] = buckets end end, opts['rates']) end @@ -546,8 +616,24 @@ if opts then 'Ratelimit whitelist user map') end + settings.custom_keywords = {} if opts['custom_keywords'] then - settings.custom_keywords = dofile(opts['custom_keywords']) + local ret, res_or_err = pcall(loadfile(opts['custom_keywords'])) + + if ret then + opts['custom_keywords'] = {} + if type(res_or_err) == 'table' then + for k,hdl in pairs(res_or_err) do + settings['custom_keywords'][k] = hdl + end + elseif type(res_or_err) == 'function' then + settings['custom_keywords']['custom'] = res_or_err + end + else + rspamd_logger.errx(rspamd_config, 'cannot execute %s: %s', + opts['custom_keywords'], res_or_err) + settings['custom_keywords'] = {} + end end if opts['message_func'] then @@ -563,7 +649,7 @@ if opts then local s = { type = 'prefilter,nostat', name = 'RATELIMIT_CHECK', - priority = 4, + priority = 7, callback = ratelimit_cb, flags = 'empty', } @@ -580,14 +666,6 @@ if opts then name = 'RATELIMIT_UPDATE', callback = ratelimit_update_cb, } - - if settings.custom_keywords then - for _, v in pairs(settings.custom_keywords) do - if type(v) == 'table' and type(v['init']) == 'function' then - v['init']() - end - end - end end end diff --git a/src/plugins/lua/reputation.lua b/src/plugins/lua/reputation.lua index 28d0401ea..2ef7fbfd7 100644 --- a/src/plugins/lua/reputation.lua +++ b/src/plugins/lua/reputation.lua @@ -1,5 +1,5 @@ --[[ -Copyright (c) 2017, Vsevolod Stakhov <vsevolod@highsecure.ru> +Copyright (c) 2017-2018, Vsevolod Stakhov <vsevolod@highsecure.ru> Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -55,26 +55,42 @@ local function generic_reputation_calc(token, rule, mult) return score end +local function add_symbol_score(task, rule, mult, params) + if not params then params = {tostring(mult)}; + + end + if rule.config.split_symbols then + if mult >= 0 then + task:insert_result(rule.symbol .. '_SPAM', mult, params) + else + task:insert_result(rule.symbol .. '_HAM', mult, params) + end + else + task:insert_result(rule.symbol, mult, params) + end +end + -- DKIM Selector functions local gr local function gen_dkim_queries(task, rule) - local dkim_trace = task:get_symbol('DKIM_TRACE') + local dkim_trace = (task:get_symbol('DKIM_TRACE') or E)[1] local lpeg = require 'lpeg' local ret = {} if not gr then local semicolon = lpeg.P(':') - local domain = lpeg.C((1 - semicolon)^0) + local domain = lpeg.C((1 - semicolon)^1) local res = lpeg.S'+-?~' local function res_to_label(ch) if ch == '+' then return 'a' elseif ch == '-' then return 'r' - else return 'u' end + + return 'u' end - gr = domain * semicolon * lpeg.C(res / res_to_label) + gr = domain * semicolon * (lpeg.C(res^1) / res_to_label) end if dkim_trace and dkim_trace.options then @@ -97,6 +113,8 @@ local function dkim_reputation_filter(task, rule) local rep_accepted = 0.0 local rep_rejected = 0.0 + rspamd_logger.debugm(N, task, 'dkim reputation tokens: %s', requests) + local function tokens_cb(err, token, values) nchecked = nchecked + 1 @@ -116,9 +134,9 @@ local function dkim_reputation_filter(task, rule) -- Set local reputation symbol if rep_accepted > 0 or rep_rejected > 0 then if rep_accepted > rep_rejected then - task:insert_result(rule.symbol, -(rep_accepted - rep_rejected)) + add_symbol_score(task, rule, -(rep_accepted - rep_rejected)) else - task:insert_result(rule.symbol, (rep_rejected - rep_accepted)) + add_symbol_score(task, rule, (rep_rejected - rep_accepted)) end -- Store results for future DKIM results adjustments @@ -167,7 +185,7 @@ local function dkim_reputation_postfilter(task, rule) local accept_adjustment = task:get_mempool():get_variable("dkim_reputation_accept") if sym_accepted and accept_adjustment then - local final_adjustment = rule.cfg.max_accept_adjustment * + local final_adjustment = rule.config.max_accept_adjustment * rspamd_util.tanh(tonumber(accept_adjustment)) task:adjust_result('R_DKIM_ALLOW', sym_accepted.score * final_adjustment) end @@ -176,7 +194,7 @@ local function dkim_reputation_postfilter(task, rule) local reject_adjustment = task:get_mempool():get_variable("dkim_reputation_reject") if sym_rejected and reject_adjustment then - local final_adjustment = rule.cfg.max_reject_adjustment * + local final_adjustment = rule.config.max_reject_adjustment * rspamd_util.tanh(tonumber(reject_adjustment)) task:adjust_result('R_DKIM_REJECT', sym_rejected.score * final_adjustment) end @@ -271,7 +289,7 @@ local function url_reputation_filter(task, rule) if math.abs(score) > 1e-3 then -- TODO: add description - task:insert_result(rule.symbol, score) + add_symbol_score(task, rule, score) end end end @@ -343,6 +361,8 @@ local function ip_reputation_init(rule) 'map', 'IP score whitelisted ASNs/countries') end + + return true end local function ip_reputation_filter(task, rule) @@ -402,7 +422,7 @@ local function ip_reputation_filter(task, rule) end if math.abs(score) > 0.001 then - task:insert_result(rule.symbol, score, table.concat(description_t, ', ')) + add_symbol_score(task, rule, score, table.concat(description_t, ', ')) end end @@ -543,10 +563,96 @@ local ip_selector = { idempotent = ip_reputation_idempotent -- used to set scores } +-- SPF Selector functions + +local function spf_reputation_filter(task, rule) + local spf_record = task:get_mempool():get_variable('spf_record') + local spf_allow = task:has_symbol('R_SPF_ALLOW') + + -- Don't care about bad/missing spf + if not spf_record or not spf_allow then return end + + local cr = require "rspamd_cryptobox_hash" + local hkey = cr.create(spf_record):base32():sub(1, 32) + + rspamd_logger.debugm(N, task, 'check spf record %s -> %s', spf_record, hkey) + + local function tokens_cb(err, token, values) + if values then + local score = generic_reputation_calc(values, rule, 1.0) + + if math.abs(score) > 1e-3 then + -- TODO: add description + add_symbol_score(task, rule, score) + end + end + end + + rule.backend.get_token(task, rule, hkey, tokens_cb) +end + +local function spf_reputation_idempotent(task, rule) + local action = task:get_metric_action() + local spf_record = task:get_mempool():get_variable('spf_record') + local spf_allow = task:has_symbol('R_SPF_ALLOW') + local token = { + } + local cfg = rule.selector.config + local need_set = false + + if not spf_record or not spf_allow then return end + + -- TODO: take metric score into consideration + local k = cfg.keys_map[action] + + if k then + token[k] = 1.0 + need_set = true + end + + if need_set then + local cr = require "rspamd_cryptobox_hash" + local hkey = cr.create(spf_record):base32():sub(1, 32) + + rspamd_logger.debugm(N, task, 'set spf record %s -> %s = %s', + spf_record, hkey, token) + rule.backend.set_token(task, rule, hkey, token) + end +end + + +local spf_selector = { + config = { + -- keys map between actions and hash elements in bucket, + -- h is for ham, + -- s is for spam, + -- p is for probable spam + keys_map = { + ['reject'] = 's', + ['add header'] = 'p', + ['rewrite subject'] = 'p', + ['no action'] = 'h' + }, + symbol = 'SPF_SCORE', -- symbol to be inserted + lower_bound = 10, -- minimum number of messages to be scored + min_score = nil, + max_score = nil, + outbound = true, + inbound = true, + max_accept_adjustment = 2.0, -- How to adjust accepted DKIM score + max_reject_adjustment = 3.0 -- How to adjust rejected DKIM score + }, + dependencies = {"R_SPF_ALLOW"}, + filter = spf_reputation_filter, -- used to get scores + idempotent = spf_reputation_idempotent -- used to set scores +} + + local selectors = { ip = ip_selector, url = url_selector, - dkim = dkim_selector + dkim = dkim_selector, + spf = spf_selector } local function reputation_dns_init(rule, _, _, _) @@ -655,7 +761,15 @@ local function reputation_dns_get_token(task, rule, token, continuation_cb) end local function reputation_redis_init(rule, cfg, ev_base, worker) - if not redis_params then + local our_redis_params = {} + + if not lua_redis.try_load_redis_servers(rule.backend.config, + rspamd_config, our_redis_params) then + our_redis_params = redis_params + end + if not our_redis_params then + rspamd_logger.errx(rspamd_config, 'cannot init redis for reputation rule: %s', + rule) return false end -- Init scripts for buckets @@ -686,7 +800,7 @@ end return result ]]) rule.backend.script_get = lua_redis.add_redis_script(table.concat(redis_script_tbl, '\n'), - redis_params) + our_redis_params) redis_script_tbl = {} local redis_set_script_tpl = [[ @@ -723,7 +837,7 @@ redis.call('HSET', key, 'last', now) end rule.backend.script_set = lua_redis.add_redis_script(table.concat(redis_script_tbl, '\n'), - redis_params) + our_redis_params) return true end @@ -741,6 +855,8 @@ local function reputation_redis_get_token(task, rule, token, continuation_cb) values[data[i]] = ndata end end + rspamd_logger.debugm(N, task, 'got values for key %s -> %s', + key, values) continuation_cb(nil, key, values) else rspamd_logger.errx(task, 'invalid type while getting reputation keys %s: %s', @@ -753,6 +869,8 @@ local function reputation_redis_get_token(task, rule, token, continuation_cb) key, err) continuation_cb(err, key, nil) else + rspamd_logger.errx(task, 'got error while getting reputation keys %s: %s', + key, "unknown error") continuation_cb("unknown error", key, nil) end end @@ -789,6 +907,8 @@ local function reputation_redis_set_token(task, rule, token, values, continuatio table.insert(args, k) table.insert(args, v) end + rspamd_logger.debugm(N, task, 'set values for key %s -> %s', + key, values) local ret = lua_redis.exec_redis_script(rule.backend.script_set, {task = task, is_write = true}, redis_set_cb, @@ -955,7 +1075,7 @@ local function parse_rule(name, tbl) if rule.config.whitelisted_ip then rule.config.whitelisted_ip_map = lua_maps.rspamd_map_add_from_ucl(rule.whitelisted_ip, 'radix', - 'Reputation whiteliist for ' .. name) + 'Reputation whitelist for ' .. name) end local symbol = name @@ -976,6 +1096,8 @@ local function parse_rule(name, tbl) if rule.selector.init then if not rule.selector.init(rule, cfg, ev_base, worker) then rule.enabled = false + rspamd_logger.errx(rspamd_config, 'Cannot init selector %s (backend %s) for symbol %s', + sel_type, bk_type, rule.symbol) else rule.enabled = true end @@ -983,19 +1105,39 @@ local function parse_rule(name, tbl) if rule.backend.init then if not rule.backend.init(rule, cfg, ev_base, worker) then rule.enabled = false + rspamd_logger.errx(rspamd_config, 'Cannot init backend (%s) for rule %s for symbol %s', + bk_type, sel_type, rule.symbol) else rule.enabled = true end end + + if rule.enabled then + rspamd_logger.infox(rspamd_config, 'Enable %s (%s backend) rule for symbol %s', + sel_type, bk_type, rule.symbol) + end end) -- We now generate symbol for checking - rspamd_config:register_symbol{ + local id = rspamd_config:register_symbol{ name = symbol, type = 'normal', callback = callback_gen(reputation_filter_cb, rule), } + if rule.config.split_symbols then + rspamd_config:register_symbol{ + name = symbol .. '_HAM', + type = 'virtual', + parent = id, + } + rspamd_config:register_symbol{ + name = symbol .. '_SPAM', + type = 'virtual', + parent = id, + } + end + if rule.selector.dependencies then fun.each(function(d) rspamd_config:register_dependency(symbol, d) @@ -1020,11 +1162,9 @@ local function parse_rule(name, tbl) } end - rspamd_logger.infox('Enable %s(%s backend) rule for symbol %s', - sel_type, bk_type, rule.symbol) end -redis_params = rspamd_parse_redis_server('reputation') +redis_params = lua_redis.parse_redis_server('reputation') local opts = rspamd_config:get_all_opt("reputation") -- Initialization part diff --git a/src/plugins/lua/settings.lua b/src/plugins/lua/settings.lua index 8e4009162..468ab6583 100644 --- a/src/plugins/lua/settings.lua +++ b/src/plugins/lua/settings.lua @@ -55,6 +55,24 @@ local function apply_settings(task, to_apply) task:set_flag(fl) end end + + if to_apply.symbols then + -- Add symbols, specified in the settings + if #to_apply.symbols > 0 then + fun.each(function(val) + task:insert_result(val, 1.0) + end, + fun.filter(function(elt) return type(elt) == 'string' end, + to_apply.symbols)) + else + -- Object like symbols + fun.each(function(k, val) + task:insert_result(k, val.score or 1.0, val.options or {}) + end, + fun.filter(function(_, elt) return type(elt) == 'table' end, + to_apply.symbols)) + end + end end -- Checks for overridden settings within query params and returns 'true' if diff --git a/src/plugins/lua/spamassassin.lua b/src/plugins/lua/spamassassin.lua index 649abc51f..4d545b232 100644 --- a/src/plugins/lua/spamassassin.lua +++ b/src/plugins/lua/spamassassin.lua @@ -152,10 +152,6 @@ local function replace_symbol(s) return rspamd_symbol, true end -local function trim(s) - return s:match "^%s*(.-)%s*$" -end - local ffi if type(jit) == 'table' then ffi = require("ffi") @@ -702,7 +698,9 @@ local function process_sa_conf(f) local if_nested = 0 for l in f:lines() do (function () - l = trim(l) + l = lua_util.rspamd_str_trim(l) + -- Replace bla=~/re/ with bla =~ /re/ (#2372) + l = l:gsub('([^%s])%s*([=!]~)%s*([^%s])', '%1 %2 %3') if string.len(l) == 0 or string.sub(l, 1, 1) == '#' then return @@ -1568,7 +1566,7 @@ local function post_process() else local rspamd_symbol, replaced_symbol = replace_symbol(a) if replaced_symbol then - external_deps[a] = {rspamd_symbol} + external_deps[a] = {[rspamd_symbol] = true} else external_deps[a] = {} end diff --git a/src/plugins/lua/url_redirector.lua b/src/plugins/lua/url_redirector.lua index d0984bd15..89216b28b 100644 --- a/src/plugins/lua/url_redirector.lua +++ b/src/plugins/lua/url_redirector.lua @@ -275,7 +275,7 @@ if opts then lua_util.disable_module(N, "redis") else if rspamd_plugins.surbl then - rspamd_plugins.surbl.register_redirect(url_redirector_handler) + rspamd_plugins.surbl.register_redirect(rspamd_config, url_redirector_handler) else rspamd_logger.infox(rspamd_config, 'surbl module is not enabled, disabling module') lua_util.disable_module(N, "fail") diff --git a/src/plugins/lua/whitelist.lua b/src/plugins/lua/whitelist.lua index 7637bb555..69c37c5dc 100644 --- a/src/plugins/lua/whitelist.lua +++ b/src/plugins/lua/whitelist.lua @@ -135,6 +135,9 @@ local function whitelist_cb(symbol, rule, task) if tld then found, mult = find_domain(tld) + if not found then + found, mult = find_domain(val) + end end end end, dkim_opts) diff --git a/src/plugins/regexp.c b/src/plugins/regexp.c index 57b8e524e..915305aa3 100644 --- a/src/plugins/regexp.c +++ b/src/plugins/regexp.c @@ -36,12 +36,9 @@ struct regexp_module_item { struct regexp_ctx { struct module_ctx ctx; - rspamd_mempool_t *regexp_pool; gsize max_size; }; -static struct regexp_ctx *regexp_module_ctx = NULL; - static void process_regexp_item (struct rspamd_task *task, void *user_data); @@ -51,14 +48,23 @@ gint regexp_module_config (struct rspamd_config *cfg); gint regexp_module_reconfig (struct rspamd_config *cfg); module_t regexp_module = { - "regexp", - regexp_module_init, - regexp_module_config, - regexp_module_reconfig, - NULL, - RSPAMD_MODULE_VER + "regexp", + regexp_module_init, + regexp_module_config, + regexp_module_reconfig, + NULL, + RSPAMD_MODULE_VER, + (guint)-1, }; + +static inline struct regexp_ctx * +regexp_get_context (struct rspamd_config *cfg) +{ + return (struct regexp_ctx *)g_ptr_array_index (cfg->c_modules, + regexp_module.ctx_offset); +} + /* Process regexp expression */ static gboolean read_regexp_expression (rspamd_mempool_t * pool, @@ -91,10 +97,10 @@ read_regexp_expression (rspamd_mempool_t * pool, gint regexp_module_init (struct rspamd_config *cfg, struct module_ctx **ctx) { - if (regexp_module_ctx == NULL) { - regexp_module_ctx = g_malloc (sizeof (struct regexp_ctx)); - regexp_module_ctx->regexp_pool = rspamd_mempool_new (rspamd_mempool_suggest_size (), NULL); - } + struct regexp_ctx *regexp_module_ctx; + + regexp_module_ctx = rspamd_mempool_alloc0 (cfg->cfg_pool, + sizeof (*regexp_module_ctx)); *ctx = (struct module_ctx *)regexp_module_ctx; @@ -124,6 +130,7 @@ regexp_module_init (struct rspamd_config *cfg, struct module_ctx **ctx) gint regexp_module_config (struct rspamd_config *cfg) { + struct regexp_ctx *regexp_module_ctx = regexp_get_context (cfg); struct regexp_module_item *cur_item = NULL; const ucl_object_t *sec, *value, *elt; ucl_object_iter_t it = NULL; @@ -152,12 +159,12 @@ regexp_module_config (struct rspamd_config *cfg) msg_warn_config ("regexp module is now single threaded, max_threads is ignored"); } else if (value->type == UCL_STRING) { - cur_item = rspamd_mempool_alloc0 (regexp_module_ctx->regexp_pool, + cur_item = rspamd_mempool_alloc0 (cfg->cfg_pool, sizeof (struct regexp_module_item)); cur_item->symbol = ucl_object_key (value); cur_item->magic = rspamd_regexp_cb_magic; - if (!read_regexp_expression (regexp_module_ctx->regexp_pool, + if (!read_regexp_expression (cfg->cfg_pool, cur_item, ucl_object_key (value), ucl_obj_tostring (value), cfg)) { res = FALSE; @@ -174,7 +181,7 @@ regexp_module_config (struct rspamd_config *cfg) } else if (value->type == UCL_USERDATA) { /* Just a lua function */ - cur_item = rspamd_mempool_alloc0 (regexp_module_ctx->regexp_pool, + cur_item = rspamd_mempool_alloc0 (cfg->cfg_pool, sizeof (struct regexp_module_item)); cur_item->magic = rspamd_regexp_cb_magic; cur_item->symbol = ucl_object_key (value); @@ -203,12 +210,12 @@ regexp_module_config (struct rspamd_config *cfg) elt = ucl_object_lookup_any (value, "regexp", "re", NULL); if (elt != NULL && ucl_object_type (elt) == UCL_STRING) { - cur_item = rspamd_mempool_alloc0 (regexp_module_ctx->regexp_pool, + cur_item = rspamd_mempool_alloc0 (cfg->cfg_pool, sizeof (struct regexp_module_item)); cur_item->symbol = ucl_object_key (value); cur_item->magic = rspamd_regexp_cb_magic; - if (!read_regexp_expression (regexp_module_ctx->regexp_pool, + if (!read_regexp_expression (cfg->cfg_pool, cur_item, ucl_object_key (value), ucl_obj_tostring (elt), cfg)) { res = FALSE; @@ -228,7 +235,7 @@ regexp_module_config (struct rspamd_config *cfg) is_lua = TRUE; nlua ++; cur_item = rspamd_mempool_alloc0 ( - regexp_module_ctx->regexp_pool, + cfg->cfg_pool, sizeof (struct regexp_module_item)); cur_item->magic = rspamd_regexp_cb_magic; cur_item->symbol = ucl_object_key (value); @@ -338,14 +345,6 @@ regexp_module_config (struct rspamd_config *cfg) gint regexp_module_reconfig (struct rspamd_config *cfg) { - struct module_ctx saved_ctx; - - saved_ctx = regexp_module_ctx->ctx; - rspamd_mempool_delete (regexp_module_ctx->regexp_pool); - memset (regexp_module_ctx, 0, sizeof (*regexp_module_ctx)); - regexp_module_ctx->ctx = saved_ctx; - regexp_module_ctx->regexp_pool = rspamd_mempool_new (rspamd_mempool_suggest_size (), NULL); - return regexp_module_config (cfg); } diff --git a/src/plugins/spf.c b/src/plugins/spf.c index 1c8ec1fa9..5ec5bfcfc 100644 --- a/src/plugins/spf.c +++ b/src/plugins/spf.c @@ -55,7 +55,6 @@ struct spf_ctx { const gchar *symbol_na; const gchar *symbol_permfail; - rspamd_mempool_t *spf_pool; struct rspamd_radix_map_helper *whitelist_ip; rspamd_lru_hash_t *spf_hash; @@ -63,8 +62,6 @@ struct spf_ctx { gboolean check_authed; }; -static struct spf_ctx *spf_module_ctx = NULL; - static void spf_symbol_callback (struct rspamd_task *task, void *unused); /* Initialization */ @@ -73,23 +70,30 @@ gint spf_module_config (struct rspamd_config *cfg); gint spf_module_reconfig (struct rspamd_config *cfg); module_t spf_module = { - "spf", - spf_module_init, - spf_module_config, - spf_module_reconfig, - NULL, - RSPAMD_MODULE_VER + "spf", + spf_module_init, + spf_module_config, + spf_module_reconfig, + NULL, + RSPAMD_MODULE_VER, + (guint)-1, }; +static inline struct spf_ctx * +spf_get_context (struct rspamd_config *cfg) +{ + return (struct spf_ctx *)g_ptr_array_index (cfg->c_modules, + spf_module.ctx_offset); +} + + gint spf_module_init (struct rspamd_config *cfg, struct module_ctx **ctx) { - if (spf_module_ctx == NULL) { - spf_module_ctx = g_malloc (sizeof (struct spf_ctx)); - - spf_module_ctx->spf_pool = rspamd_mempool_new (rspamd_mempool_suggest_size (), NULL); - } + struct spf_ctx *spf_module_ctx; + spf_module_ctx = rspamd_mempool_alloc0 (cfg->cfg_pool, + sizeof (*spf_module_ctx)); *ctx = (struct module_ctx *)spf_module_ctx; rspamd_rcl_add_doc_by_path (cfg, @@ -194,6 +198,7 @@ spf_module_config (struct rspamd_config *cfg) const ucl_object_t *value; gint res = TRUE, cb_id; guint cache_size; + struct spf_ctx *spf_module_ctx = spf_get_context (cfg); if (!rspamd_config_is_module_enabled (cfg, "spf")) { return TRUE; @@ -323,22 +328,19 @@ spf_module_config (struct rspamd_config *cfg) msg_info_config ("init internal spf module"); + rspamd_mempool_add_destructor (cfg->cfg_pool, + (rspamd_mempool_destruct_t)rspamd_lru_hash_destroy, + spf_module_ctx->spf_hash); + rspamd_mempool_add_destructor (cfg->cfg_pool, + (rspamd_mempool_destruct_t)rspamd_map_helper_destroy_radix, + spf_module_ctx->whitelist_ip); + return res; } gint spf_module_reconfig (struct rspamd_config *cfg) { - struct module_ctx saved_ctx; - - saved_ctx = spf_module_ctx->ctx; - rspamd_mempool_delete (spf_module_ctx->spf_pool); - rspamd_lru_hash_destroy (spf_module_ctx->spf_hash); - rspamd_map_helper_destroy_radix (spf_module_ctx->whitelist_ip); - memset (spf_module_ctx, 0, sizeof (*spf_module_ctx)); - spf_module_ctx->ctx = saved_ctx; - spf_module_ctx->spf_pool = rspamd_mempool_new (rspamd_mempool_suggest_size (), NULL); - return spf_module_config (cfg); } @@ -351,6 +353,7 @@ spf_check_element (struct spf_resolved *rec, struct spf_addr *addr, gchar *spf_result; guint af, mask, bmask, addrlen; const gchar *spf_message, *spf_symbol; + struct spf_ctx *spf_module_ctx = spf_get_context (task->cfg); if (task->from_addr == NULL) { return FALSE; @@ -497,6 +500,7 @@ spf_plugin_callback (struct spf_resolved *record, struct rspamd_task *task, { struct spf_resolved *l; struct rspamd_async_watcher *w = ud; + struct spf_ctx *spf_module_ctx = spf_get_context (task->cfg); if (record && record->na) { rspamd_task_insert_result (task, @@ -556,6 +560,7 @@ spf_symbol_callback (struct rspamd_task *task, void *unused) struct spf_resolved *l; struct rspamd_async_watcher *w; gint *dmarc_checks; + struct spf_ctx *spf_module_ctx = spf_get_context (task->cfg); /* Allow dmarc */ dmarc_checks = rspamd_mempool_get_variable (task->task_pool, diff --git a/src/plugins/surbl.c b/src/plugins/surbl.c index d0bdd3b8c..81496f0a9 100644 --- a/src/plugins/surbl.c +++ b/src/plugins/surbl.c @@ -82,13 +82,11 @@ struct surbl_ctx { const gchar *redirector_symbol; GHashTable **exceptions; struct rspamd_hash_map_helper *whitelist; - void *redirector_map_data; GHashTable *redirector_tlds; guint use_redirector; guint max_redirected_urls; gint redirector_cbid; struct upstream_list *redirectors; - rspamd_mempool_t *surbl_pool; }; struct suffix_item { @@ -110,12 +108,14 @@ struct dns_param { gchar *host_resolve; struct suffix_item *suffix; struct rspamd_async_watcher *w; + struct surbl_module_ctx *ctx; }; struct redirector_param { struct rspamd_url *url; struct rspamd_task *task; struct upstream *redirector; + struct surbl_ctx *ctx; struct rspamd_http_connection *conn; GHashTable *tree; struct suffix_item *suffix; @@ -131,7 +131,6 @@ struct surbl_bit_item { #define SURBL_REDIRECTOR_CALLBACK "SURBL_REDIRECTOR_CALLBACK" -static struct surbl_ctx *surbl_module_ctx = NULL; static const guint64 rspamd_surbl_cb_magic = 0xe09b8536f80de0d1ULL; static const gchar *rspamd_surbl_default_monitored = "facebook.com"; static const guint default_max_redirected_urls = 10; @@ -166,14 +165,22 @@ gint surbl_module_config (struct rspamd_config *cfg); gint surbl_module_reconfig (struct rspamd_config *cfg); module_t surbl_module = { - "surbl", - surbl_module_init, - surbl_module_config, - surbl_module_reconfig, - NULL, - RSPAMD_MODULE_VER + "surbl", + surbl_module_init, + surbl_module_config, + surbl_module_reconfig, + NULL, + RSPAMD_MODULE_VER, + (guint)-1, }; +static inline struct surbl_ctx * +surbl_get_context (struct rspamd_config *cfg) +{ + return (struct surbl_ctx *)g_ptr_array_index (cfg->c_modules, + surbl_module.ctx_offset); +} + static void exceptions_free_value (gpointer v) { @@ -230,14 +237,19 @@ read_exceptions_list (gchar * chunk, if (data->cur_data == NULL) { t = data->prev_data; - for (i = 0; i < MAX_LEVELS; i++) { - if (t[i] != NULL) { - g_hash_table_destroy (t[i]); + if (t) { + for (i = 0; i < MAX_LEVELS; i++) { + if (t[i] != NULL) { + g_hash_table_destroy (t[i]); + } + t[i] = NULL; } - t[i] = NULL; + + g_free (t); } - data->cur_data = data->prev_data; + data->prev_data = NULL; + data->cur_data = g_malloc0 (MAX_LEVELS * sizeof (GHashTable *)); } return rspamd_parse_kv_list ( @@ -270,6 +282,25 @@ fin_exceptions_list (struct map_cb_data *data) } static void +dtor_exceptions_list (struct map_cb_data *data) +{ + GHashTable **t; + gint i; + + if (data->cur_data) { + t = data->cur_data; + for (i = 0; i < MAX_LEVELS; i++) { + if (t[i] != NULL) { + g_hash_table_destroy (t[i]); + } + t[i] = NULL; + } + + g_free (t); + } +} + +static void redirector_insert (gpointer st, gconstpointer key, gconstpointer value) { GHashTable *tld_hash = st; @@ -356,27 +387,34 @@ fin_redirectors_list (struct map_cb_data *data) g_hash_table_unref (tld_hash); } +} + +void +dtor_redirectors_list (struct map_cb_data *data) +{ + GHashTable *tld_hash; - tld_hash = data->cur_data; - surbl_module_ctx->redirector_tlds = tld_hash; + if (data->cur_data) { + tld_hash = data->cur_data; + + g_hash_table_unref (tld_hash); + } } gint surbl_module_init (struct rspamd_config *cfg, struct module_ctx **ctx) { - surbl_module_ctx = g_malloc0 (sizeof (struct surbl_ctx)); + struct surbl_ctx *surbl_module_ctx; + + surbl_module_ctx = rspamd_mempool_alloc0 (cfg->cfg_pool, + sizeof (struct surbl_ctx)); surbl_module_ctx->use_redirector = 0; surbl_module_ctx->suffixes = NULL; - surbl_module_ctx->surbl_pool = rspamd_mempool_new (rspamd_mempool_suggest_size (), NULL); surbl_module_ctx->redirectors = NULL; surbl_module_ctx->whitelist = NULL; - rspamd_mempool_add_destructor (surbl_module_ctx->surbl_pool, - (rspamd_mempool_destruct_t) rspamd_map_helper_destroy_hash, - surbl_module_ctx->whitelist); - surbl_module_ctx->exceptions = rspamd_mempool_alloc0 ( - surbl_module_ctx->surbl_pool, MAX_LEVELS * sizeof (GHashTable *)); + surbl_module_ctx->exceptions = NULL; surbl_module_ctx->redirector_cbid = -1; @@ -589,6 +627,7 @@ surbl_module_parse_rule (const ucl_object_t* value, struct rspamd_config* cfg) const gchar* ip_val, *monitored_domain = NULL; struct surbl_bit_item* new_bit; ucl_object_t *ropts; + struct surbl_ctx *surbl_module_ctx = surbl_get_context (cfg); LL_FOREACH(value, cur_rule) { monitored_domain = NULL; @@ -607,15 +646,15 @@ surbl_module_parse_rule (const ucl_object_t* value, struct rspamd_config* cfg) continue; } - new_suffix = rspamd_mempool_alloc0 (surbl_module_ctx->surbl_pool, + new_suffix = rspamd_mempool_alloc0 (cfg->cfg_pool, sizeof (struct suffix_item)); new_suffix->magic = rspamd_surbl_cb_magic; new_suffix->suffix = rspamd_mempool_strdup ( - surbl_module_ctx->surbl_pool, ucl_obj_tostring (cur)); + cfg->cfg_pool, ucl_obj_tostring (cur)); new_suffix->options = 0; new_suffix->bits = g_array_new (FALSE, FALSE, sizeof (struct surbl_bit_item)); - rspamd_mempool_add_destructor (surbl_module_ctx->surbl_pool, + rspamd_mempool_add_destructor (cfg->cfg_pool, (rspamd_mempool_destruct_t )rspamd_array_free_hard, new_suffix->bits); @@ -623,7 +662,7 @@ surbl_module_parse_rule (const ucl_object_t* value, struct rspamd_config* cfg) if (cur == NULL) { if (ucl_object_key (value)) { new_suffix->symbol = rspamd_mempool_strdup ( - surbl_module_ctx->surbl_pool, + cfg->cfg_pool, ucl_object_key (value)); } else { @@ -631,12 +670,12 @@ surbl_module_parse_rule (const ucl_object_t* value, struct rspamd_config* cfg) "surbl rule for suffix %s lacks symbol, using %s as symbol", new_suffix->suffix, DEFAULT_SURBL_SYMBOL); new_suffix->symbol = rspamd_mempool_strdup ( - surbl_module_ctx->surbl_pool, DEFAULT_SURBL_SYMBOL); + cfg->cfg_pool, DEFAULT_SURBL_SYMBOL); } } else { new_suffix->symbol = rspamd_mempool_strdup ( - surbl_module_ctx->surbl_pool, ucl_obj_tostring (cur)); + cfg->cfg_pool, ucl_obj_tostring (cur)); } cur = ucl_object_lookup (cur_rule, "options"); @@ -680,7 +719,7 @@ surbl_module_parse_rule (const ucl_object_t* value, struct rspamd_config* cfg) ucl_object_insert_key (ropts, ucl_object_fromstring ("nxdomain"), "rcode", 0, false); - rspamd_mempool_add_destructor (surbl_module_ctx->surbl_pool, + rspamd_mempool_add_destructor (cfg->cfg_pool, (rspamd_mempool_destruct_t )ucl_object_unref, ropts); @@ -719,11 +758,11 @@ surbl_module_parse_rule (const ucl_object_t* value, struct rspamd_config* cfg) gchar* p; bit = ucl_obj_toint (cur_bit); new_bit = rspamd_mempool_alloc ( - surbl_module_ctx->surbl_pool, + cfg->cfg_pool, sizeof(struct surbl_bit_item)); new_bit->bit = bit; new_bit->symbol = rspamd_mempool_strdup ( - surbl_module_ctx->surbl_pool, + cfg->cfg_pool, ucl_object_key (cur_bit)); /* Convert to uppercase */ p = new_bit->symbol; @@ -747,7 +786,7 @@ surbl_module_parse_rule (const ucl_object_t* value, struct rspamd_config* cfg) guint32 bit; new_suffix->ips = g_hash_table_new (g_int_hash, g_int_equal); - rspamd_mempool_add_destructor (surbl_module_ctx->surbl_pool, + rspamd_mempool_add_destructor (cfg->cfg_pool, (rspamd_mempool_destruct_t )g_hash_table_unref, new_suffix->ips); @@ -756,7 +795,7 @@ surbl_module_parse_rule (const ucl_object_t* value, struct rspamd_config* cfg) gchar* p; ip_val = ucl_obj_tostring (cur_bit); new_bit = rspamd_mempool_alloc ( - surbl_module_ctx->surbl_pool, + cfg->cfg_pool, sizeof(struct surbl_bit_item)); if (inet_pton (AF_INET, ip_val, &bit) != 1) { msg_err_config ("cannot parse ip %s: %s", ip_val, @@ -765,7 +804,7 @@ surbl_module_parse_rule (const ucl_object_t* value, struct rspamd_config* cfg) } new_bit->bit = bit; new_bit->symbol = rspamd_mempool_strdup ( - surbl_module_ctx->surbl_pool, + cfg->cfg_pool, ucl_object_key (cur_bit)); /* Convert to uppercase */ p = new_bit->symbol; @@ -858,6 +897,7 @@ surbl_module_config (struct rspamd_config *cfg) const gchar *redir_val; gint nrules = 0; lua_State *L; + struct surbl_ctx *surbl_module_ctx = surbl_get_context (cfg); if (!rspamd_config_is_module_enabled (cfg, "surbl")) { return TRUE; @@ -893,7 +933,7 @@ surbl_module_config (struct rspamd_config *cfg) if ((value = rspamd_config_get_module_opt (cfg, "surbl", "redirector")) != NULL) { surbl_module_ctx->redirectors = rspamd_upstreams_create (cfg->ups_ctx); - rspamd_mempool_add_destructor (surbl_module_ctx->surbl_pool, + rspamd_mempool_add_destructor (cfg->cfg_pool, (rspamd_mempool_destruct_t)rspamd_upstreams_destroy, surbl_module_ctx->redirectors); LL_FOREACH (value, cur) @@ -946,8 +986,11 @@ surbl_module_config (struct rspamd_config *cfg) rspamd_config_get_module_opt (cfg, "surbl", "redirector_hosts_map")) != NULL) { if (!rspamd_map_add_from_ucl (cfg, value, - "SURBL redirectors list", read_redirectors_list, fin_redirectors_list, - (void **)&surbl_module_ctx->redirector_map_data)) { + "SURBL redirectors list", + read_redirectors_list, + fin_redirectors_list, + dtor_redirectors_list, + (void **)&surbl_module_ctx->redirector_tlds)) { msg_warn_config ("bad redirectors map definition: %s", ucl_obj_tostring (value)); @@ -958,13 +1001,18 @@ surbl_module_config (struct rspamd_config *cfg) rspamd_config_get_module_opt (cfg, "surbl", "exceptions")) != NULL) { rspamd_map_add_from_ucl (cfg, value, "SURBL exceptions list", - read_exceptions_list, fin_exceptions_list, + read_exceptions_list, + fin_exceptions_list, + dtor_exceptions_list, (void **)&surbl_module_ctx->exceptions); } if ((value = rspamd_config_get_module_opt (cfg, "surbl", "whitelist")) != NULL) { rspamd_map_add_from_ucl (cfg, value, - "SURBL whitelist", rspamd_kv_list_read, rspamd_kv_list_fin, + "SURBL whitelist", + rspamd_kv_list_read, + rspamd_kv_list_fin, + rspamd_kv_list_dtor, (void **)&surbl_module_ctx->whitelist); } @@ -1004,7 +1052,7 @@ surbl_module_config (struct rspamd_config *cfg) } if (surbl_module_ctx->suffixes != NULL) { - rspamd_mempool_add_destructor (surbl_module_ctx->surbl_pool, + rspamd_mempool_add_destructor (cfg->cfg_pool, (rspamd_mempool_destruct_t) g_list_free, surbl_module_ctx->suffixes); } @@ -1034,28 +1082,17 @@ surbl_module_config (struct rspamd_config *cfg) gint surbl_module_reconfig (struct rspamd_config *cfg) { - /* Delete pool and objects */ - rspamd_mempool_delete (surbl_module_ctx->surbl_pool); + struct surbl_ctx *surbl_module_ctx = surbl_get_context (cfg); + /* Reinit module */ surbl_module_ctx->use_redirector = 0; surbl_module_ctx->suffixes = NULL; - surbl_module_ctx->surbl_pool = rspamd_mempool_new (rspamd_mempool_suggest_size (), NULL); - surbl_module_ctx->redirectors = NULL; surbl_module_ctx->whitelist = NULL; /* Zero exceptions hashes */ - surbl_module_ctx->exceptions = rspamd_mempool_alloc0 ( - surbl_module_ctx->surbl_pool, - MAX_LEVELS * sizeof (GHashTable *)); - /* Register destructors */ - rspamd_mempool_add_destructor (surbl_module_ctx->surbl_pool, - (rspamd_mempool_destruct_t) rspamd_map_helper_destroy_hash, - surbl_module_ctx->whitelist); - rspamd_mempool_add_destructor (surbl_module_ctx->surbl_pool, - (rspamd_mempool_destruct_t) g_hash_table_destroy, - surbl_module_ctx->redirector_tlds); - - rspamd_mempool_add_destructor (surbl_module_ctx->surbl_pool, + surbl_module_ctx->exceptions = NULL; + + rspamd_mempool_add_destructor (cfg->cfg_pool, (rspamd_mempool_destruct_t) g_list_free, surbl_module_ctx->suffixes); @@ -1067,14 +1104,15 @@ surbl_module_reconfig (struct rspamd_config *cfg) static gchar * format_surbl_request (rspamd_mempool_t * pool, - rspamd_ftok_t * hostname, - struct suffix_item *suffix, - gboolean append_suffix, - GError ** err, - gboolean forced, - GHashTable *tree, - struct rspamd_url *url, - lua_State *L) + rspamd_ftok_t * hostname, + struct suffix_item *suffix, + gboolean append_suffix, + GError ** err, + gboolean forced, + GHashTable *tree, + struct rspamd_url *url, + lua_State *L, + struct surbl_ctx *surbl_module_ctx) { GHashTable *t; gchar *result = NULL; @@ -1084,6 +1122,7 @@ format_surbl_request (rspamd_mempool_t * pool, gboolean found_exception = FALSE; rspamd_ftok_t f; + if (G_LIKELY (suffix != NULL)) { slen = strlen (suffix->suffix); } @@ -1141,7 +1180,7 @@ format_surbl_request (rspamd_mempool_t * pool, /* Not a numeric url */ result = rspamd_mempool_alloc (pool, len); /* Now we should try to check for exceptions */ - if (!forced) { + if (!forced && surbl_module_ctx->exceptions) { for (i = MAX_LEVELS - 1; i >= 0; i--) { t = surbl_module_ctx->exceptions[i]; if (t != NULL && dots_num >= i + 1) { @@ -1255,13 +1294,16 @@ format_surbl_request (rspamd_mempool_t * pool, static void make_surbl_requests (struct rspamd_url *url, struct rspamd_task *task, - struct suffix_item *suffix, gboolean forced, GHashTable *tree) + struct suffix_item *suffix, + gboolean forced, GHashTable *tree, + struct surbl_ctx *surbl_module_ctx) { gchar *surbl_req; rspamd_ftok_t f; GError *err = NULL; struct dns_param *param; + f.begin = url->host; f.len = url->hostlen; @@ -1270,8 +1312,16 @@ make_surbl_requests (struct rspamd_url *url, struct rspamd_task *task, * We need to get url real TLD, resolve it with no suffix and then * check against surbl using reverse octets printing */ - surbl_req = format_surbl_request (task->task_pool, &f, suffix, FALSE, - &err, forced, tree, url, task->cfg->lua_state); + surbl_req = format_surbl_request (task->task_pool, + &f, + suffix, + FALSE, + &err, + forced, + tree, + url, + task->cfg->lua_state, + surbl_module_ctx); if (surbl_req == NULL) { if (err != NULL) { @@ -1313,7 +1363,8 @@ make_surbl_requests (struct rspamd_url *url, struct rspamd_task *task, forced, tree, url, - task->cfg->lua_state)) != NULL) { + task->cfg->lua_state, + surbl_module_ctx)) != NULL) { param = rspamd_mempool_alloc (task->task_pool, sizeof (struct dns_param)); param->url = url; @@ -1508,7 +1559,7 @@ surbl_redirector_error (struct rspamd_http_connection *conn, msg_err_surbl ("connection with http server %s terminated incorrectly: %e", rspamd_inet_address_to_string (rspamd_upstream_addr (param->redirector)), err); - rspamd_upstream_fail (param->redirector); + rspamd_upstream_fail (param->redirector, FALSE); rspamd_session_remove_event (param->task->s, free_redirector_session, param); } @@ -1585,6 +1636,7 @@ register_redirector_call (struct rspamd_url *url, struct rspamd_task *task, struct timeval *timeout; struct upstream *selected; struct rspamd_http_message *msg; + struct surbl_ctx *surbl_module_ctx = surbl_get_context (task->cfg); selected = rspamd_upstream_get (surbl_module_ctx->redirectors, RSPAMD_UPSTREAM_ROUND_ROBIN, url->host, url->hostlen); @@ -1613,6 +1665,7 @@ register_redirector_call (struct rspamd_url *url, struct rspamd_task *task, RSPAMD_HTTP_CLIENT, NULL, NULL); + param->ctx = surbl_module_ctx; msg = rspamd_http_new_message (HTTP_REQUEST); msg->url = rspamd_fstring_assign (msg->url, url->string, url->urllen); param->sock = s; @@ -1680,8 +1733,11 @@ surbl_tree_redirector_callback (gpointer key, gpointer value, void *data) rspamd_ftok_t srch; gboolean found = FALSE; gchar *found_tld; + struct surbl_ctx *surbl_module_ctx; task = param->task; + surbl_module_ctx = param->ctx; + msg_debug_surbl ("check url redirection %*s", url->urllen, url->string); if (url->hostlen <= 0) { @@ -1764,12 +1820,15 @@ surbl_tree_url_callback (gpointer key, gpointer value, void *data) struct redirector_param *param = data; struct rspamd_url *url = value; struct rspamd_task *task; + struct surbl_ctx *surbl_module_ctx; if (url->hostlen <= 0) { return; } task = param->task; + surbl_module_ctx = param->ctx; + msg_debug_surbl ("check url %*s", url->urllen, url->string); if (surbl_module_ctx->use_tags && surbl_test_tags (param->task, param, url)) { @@ -1782,7 +1841,7 @@ surbl_tree_url_callback (gpointer key, gpointer value, void *data) } make_surbl_requests (url, param->task, param->suffix, FALSE, - param->tree); + param->tree, surbl_module_ctx); } static void @@ -1794,6 +1853,7 @@ surbl_test_url (struct rspamd_task *task, void *user_data) struct rspamd_mime_text_part *part; struct html_image *img; struct rspamd_url *url; + struct surbl_ctx *surbl_module_ctx = surbl_get_context (task->cfg); if (!rspamd_monitored_alive (suffix->m)) { msg_info_surbl ("disable surbl %s as it is reported to be offline", @@ -1805,6 +1865,7 @@ surbl_test_url (struct rspamd_task *task, void *user_data) param->task = task; param->suffix = suffix; param->tree = g_hash_table_new (rspamd_strcase_hash, rspamd_strcase_equal); + param->ctx = surbl_module_ctx; rspamd_mempool_add_destructor (task->task_pool, (rspamd_mempool_destruct_t)g_hash_table_unref, param->tree); @@ -1844,6 +1905,7 @@ surbl_test_redirector (struct rspamd_task *task, void *user_data) struct rspamd_mime_text_part *part; struct html_image *img; struct rspamd_url *url; + struct surbl_ctx *surbl_module_ctx = surbl_get_context (task->cfg); if (!surbl_module_ctx->use_redirector || !surbl_module_ctx->redirector_tlds) { return; @@ -1853,6 +1915,7 @@ surbl_test_redirector (struct rspamd_task *task, void *user_data) param->task = task; param->suffix = NULL; param->redirector_requests = 0; + param->ctx = surbl_module_ctx; g_hash_table_foreach (task->urls, surbl_tree_redirector_callback, param); /* We also need to check and process img URLs */ @@ -1882,11 +1945,20 @@ surbl_test_redirector (struct rspamd_task *task, void *user_data) static gint surbl_register_redirect_handler (lua_State *L) { + struct surbl_ctx *surbl_module_ctx; + struct rspamd_config *cfg = lua_check_config (L, 1); + + if (!cfg) { + return luaL_error (L, "config is now required as the first parameter"); + } + + surbl_module_ctx = surbl_get_context (cfg); + if (surbl_module_ctx->redirector_cbid != -1) { luaL_unref (L, LUA_REGISTRYINDEX, surbl_module_ctx->redirector_cbid); } - lua_pushvalue (L, 1); + lua_pushvalue (L, 2); if (lua_type (L, -1) == LUA_TFUNCTION) { surbl_module_ctx->redirector_cbid = luaL_ref (L, LUA_REGISTRYINDEX); @@ -1912,9 +1984,11 @@ surbl_is_redirector_handler (lua_State *L) rspamd_ftok_t srch; gboolean found = FALSE; gchar *found_tld, *url_cpy; + struct surbl_ctx *surbl_module_ctx; task = lua_check_task (L, 1); url = luaL_checklstring (L, 2, &len); + surbl_module_ctx = surbl_get_context (task->cfg); if (task && url) { url_cpy = rspamd_mempool_alloc (task->task_pool, len); diff --git a/src/ragel/content_disposition.rl b/src/ragel/content_disposition.rl index 5aca57db5..614d54c68 100644 --- a/src/ragel/content_disposition.rl +++ b/src/ragel/content_disposition.rl @@ -1,6 +1,5 @@ %%{ machine content_disposition; - include smtp_whitespace "smtp_whitespace.rl"; # https://tools.ietf.org/html/rfc2045#section-5.1 diff --git a/src/ragel/content_disposition_parser.rl b/src/ragel/content_disposition_parser.rl index 71b999ce4..c35d2b232 100644 --- a/src/ragel/content_disposition_parser.rl +++ b/src/ragel/content_disposition_parser.rl @@ -86,7 +86,7 @@ } } - + include smtp_whitespace "smtp_whitespace.rl"; include content_disposition "content_disposition.rl"; main := content_disposition; diff --git a/src/ragel/smtp_addr_parser.rl b/src/ragel/smtp_addr_parser.rl index 91b100af7..737b4ddcd 100644 --- a/src/ragel/smtp_addr_parser.rl +++ b/src/ragel/smtp_addr_parser.rl @@ -2,6 +2,8 @@ machine smtp_addr_parser; + + action IP6_start {} action IP6_end {} action IP4_start {} @@ -73,6 +75,8 @@ } } + include smtp_ip "smtp_ip.rl"; + include smtp_whitespace "smtp_whitespace.rl"; include smtp_address "smtp_address.rl"; main := SMTPAddr; diff --git a/src/ragel/smtp_address.rl b/src/ragel/smtp_address.rl index 736c28b0e..f5d04f620 100644 --- a/src/ragel/smtp_address.rl +++ b/src/ragel/smtp_address.rl @@ -1,9 +1,6 @@ %%{ machine smtp_address; - include smtp_ip "smtp_ip.rl"; - include smtp_whitespace "smtp_whitespace.rl"; - # SMTP address spec # Obtained from: https://tools.ietf.org/html/rfc5321#section-4.1.2 @@ -17,6 +14,7 @@ address_literal = "[" ( IPv4_address_literal | IPv6_address_literal | General_address_literal ) >Domain_addr_start %Domain_addr_end "]"; + non_conformant_address_literal = IPv4_address_literal >Domain_addr_start %Domain_addr_end; sub_domain = Let_dig Ldh_str?; diff --git a/src/ragel/smtp_date.rl b/src/ragel/smtp_date.rl index 69227ef1e..35ed7499c 100644 --- a/src/ragel/smtp_date.rl +++ b/src/ragel/smtp_date.rl @@ -1,8 +1,6 @@ %%{ machine smtp_date; - include smtp_whitespace "smtp_whitespace.rl"; - # SMTP date spec # Obtained from: http://tools.ietf.org/html/rfc5322#section_3.3 diff --git a/src/ragel/smtp_date_parser.rl b/src/ragel/smtp_date_parser.rl index 84b63f1bd..bc6e5c8f0 100644 --- a/src/ragel/smtp_date_parser.rl +++ b/src/ragel/smtp_date_parser.rl @@ -1,6 +1,7 @@ %%{ machine smtp_date_parser; + include smtp_whitespace "smtp_whitespace.rl"; include smtp_date "smtp_date.rl"; main := date_time; diff --git a/src/ragel/smtp_received.rl b/src/ragel/smtp_received.rl index cd912db4d..b13259fed 100644 --- a/src/ragel/smtp_received.rl +++ b/src/ragel/smtp_received.rl @@ -1,10 +1,6 @@ %%{ machine smtp_received; - include smtp_whitespace "smtp_whitespace.rl"; - include smtp_ip "smtp_ip.rl"; - include smtp_date "smtp_date.rl"; - include smtp_address"smtp_address.rl"; # http://tools.ietf.org/html/rfc5321#section-4.4 @@ -21,7 +17,8 @@ Attdl_Protocol; TCP_info = address_literal >Real_IP_Start %Real_IP_End | - ( Domain >Real_Domain_Start %Real_Domain_End FWS address_literal >Real_IP_Start %Real_IP_End ); + ( Domain >Real_Domain_Start %Real_Domain_End FWS address_literal >Real_IP_Start %Real_IP_End ) | + ( non_conformant_address_literal >Real_IP_Start %Real_IP_End ); Extended_Domain = Domain >Real_Domain_Start %Real_Domain_End | # Used to be a real domain ( Domain >Reported_Domain_Start %Reported_Domain_End FWS "(" TCP_info ")" ) | # Here domain is something specified by remote side ( address_literal >Real_Domain_Start %Real_Domain_End FWS "(" TCP_info ")" ) | diff --git a/src/ragel/smtp_received_parser.rl b/src/ragel/smtp_received_parser.rl index c1cd9bc38..565a20b7f 100644 --- a/src/ragel/smtp_received_parser.rl +++ b/src/ragel/smtp_received_parser.rl @@ -225,6 +225,10 @@ } } + include smtp_whitespace "smtp_whitespace.rl"; + include smtp_ip "smtp_ip.rl"; + include smtp_date "smtp_date.rl"; + include smtp_address"smtp_address.rl"; include smtp_received "smtp_received.rl"; main := Received; diff --git a/src/rspamadm/configdump.c b/src/rspamadm/configdump.c index cd9f6d378..8e26ef0af 100644 --- a/src/rspamadm/configdump.c +++ b/src/rspamadm/configdump.c @@ -308,7 +308,8 @@ rspamadm_configdump (gint argc, gchar **argv, const struct rspamadm_command *cmd argc, argv, cfg->rcl_obj, - "plugins_stats"); + "plugins_stats", + FALSE); lua_close (L); diff --git a/src/rspamadm/confighelp.c b/src/rspamadm/confighelp.c index c77cb6091..85564cf4c 100644 --- a/src/rspamadm/confighelp.c +++ b/src/rspamadm/confighelp.c @@ -110,7 +110,8 @@ rspamadm_confighelp_show (struct rspamd_config *cfg, gint argc, gchar **argv, argc, argv, obj, - "confighelp"); + "confighelp", + TRUE); rspamd_fstring_free (out); return; @@ -201,7 +202,7 @@ rspamadm_confighelp (gint argc, gchar **argv, const struct rspamadm_command *cmd module_t *mod, **pmod; worker_t **pworker; struct module_ctx *mod_ctx; - gint i = 1, ret = 0, processed_args = 0; + gint i, ret = 0, processed_args = 0; context = g_option_context_new ( "confighelp - displays help for the configuration options"); @@ -237,16 +238,19 @@ rspamadm_confighelp (gint argc, gchar **argv, const struct rspamadm_command *cmd rspamd_rcl_add_lua_plugins_path (cfg, plugins_path, NULL); /* Init modules to get documentation strings */ + i = 0; for (pmod = cfg->compiled_modules; pmod != NULL && *pmod != NULL; pmod++) { mod = *pmod; mod_ctx = g_malloc0 (sizeof (struct module_ctx)); if (mod->module_init_func (cfg, &mod_ctx) == 0) { - g_hash_table_insert (cfg->c_modules, - (gpointer) mod->name, - mod_ctx); + g_ptr_array_add (cfg->c_modules, mod_ctx); + mod_ctx->mod = mod; + mod->ctx_offset = i++; mod_ctx->mod = mod; } + + } /* Also init all workers */ for (pworker = cfg->compiled_workers; *pworker != NULL; pworker ++) { diff --git a/src/rspamadm/control.c b/src/rspamadm/control.c index 4d7806b84..6d2849cc7 100644 --- a/src/rspamadm/control.c +++ b/src/rspamadm/control.c @@ -137,7 +137,8 @@ rspamd_control_finish_handler (struct rspamd_http_connection *conn, cbdata->argc, cbdata->argv, obj, - "fuzzy_stat"); + "fuzzy_stat", + TRUE); rspamd_fstring_free (out); ucl_object_unref (obj); diff --git a/src/rspamadm/fuzzy_convert.c b/src/rspamadm/fuzzy_convert.c index 7671678dc..1c5620730 100644 --- a/src/rspamadm/fuzzy_convert.c +++ b/src/rspamadm/fuzzy_convert.c @@ -135,7 +135,8 @@ rspamadm_fuzzyconvert (gint argc, gchar **argv, const struct rspamadm_command *c argc, argv, obj, - "fuzzy_convert"); + "fuzzy_convert", + TRUE); ucl_object_unref (obj); } diff --git a/src/rspamadm/rspamadm.c b/src/rspamadm/rspamadm.c index 68dbdeb85..9eeccb545 100644 --- a/src/rspamadm/rspamadm.c +++ b/src/rspamadm/rspamadm.c @@ -61,7 +61,7 @@ static GOptionEntry entries[] = { "Redefine UCL variable", NULL}, {"help", 'h', 0, G_OPTION_ARG_NONE, &show_help, "Show help", NULL}, - {"version", 'v', 0, G_OPTION_ARG_NONE, &show_version, + {"version", 'V', 0, G_OPTION_ARG_NONE, &show_version, "Show version", NULL}, {NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL} }; @@ -99,7 +99,12 @@ rspamadm_commands (GPtrArray *all_commands) PTR_ARRAY_FOREACH (all_commands, i, cmd) { if (!(cmd->flags & RSPAMADM_FLAG_NOHELP)) { - rspamd_printf (" %-18s %-60s\n", cmd->name, cmd->help (FALSE, cmd)); + if (cmd->flags & RSPAMADM_FLAG_LUA) { + (void)cmd->help (FALSE, cmd); + } + else { + printf (" %-18s %-60s\n", cmd->name, cmd->help (FALSE, cmd)); + } } } } @@ -201,7 +206,9 @@ rspamadm_parse_ucl_var (const gchar *option_name, gboolean rspamadm_execute_lua_ucl_subr (gpointer pL, gint argc, gchar **argv, - const ucl_object_t *res, const gchar *script_name) + const ucl_object_t *res, + const gchar *script_name, + gboolean rspamadm_subcommand) { lua_State *L = pL; gint err_idx, i, ret; @@ -214,8 +221,14 @@ rspamadm_execute_lua_ucl_subr (gpointer pL, gint argc, gchar **argv, /* Init internal rspamadm routines */ - rspamd_snprintf (str, sizeof (str), "return require \"%s.%s\"", "rspamadm", - script_name); + if (rspamadm_subcommand) { + rspamd_snprintf (str, sizeof (str), "return require \"%s.%s\"", "rspamadm", + script_name); + } + else { + rspamd_snprintf (str, sizeof (str), "return require \"%s\"", + script_name); + } if (luaL_dostring (L, str) != 0) { msg_err ("cannot execute lua script %s: %s", @@ -333,38 +346,6 @@ main (gint argc, gchar **argv, gchar **env) rspamadm_fill_internal_commands (all_commands); help_command.command_data = all_commands; - /* Setup logger */ - if (verbose) { - cfg->log_level = G_LOG_LEVEL_DEBUG; - } - else { - cfg->log_level = G_LOG_LEVEL_MESSAGE; - } - - cfg->log_type = RSPAMD_LOG_CONSOLE; - /* Avoid timestamps printing */ - cfg->log_flags = RSPAMD_LOG_FLAG_RSPAMADM; - rspamd_set_logger (cfg, process_quark, &rspamd_main->logger, - rspamd_main->server_pool); - (void) rspamd_log_open (rspamd_main->logger); - g_log_set_default_handler (rspamd_glib_log_function, rspamd_main->logger); - g_set_printerr_handler (rspamd_glib_printerr_function); - rspamd_config_post_load (cfg, - RSPAMD_CONFIG_INIT_LIBS|RSPAMD_CONFIG_INIT_URL|RSPAMD_CONFIG_INIT_NO_TLD); - - pworker = &workers[0]; - while (*pworker) { - /* Init string quarks */ - (void) g_quark_from_static_string ((*pworker)->name); - pworker++; - } - - cfg->compiled_modules = modules; - cfg->compiled_workers = workers; - - gperf_profiler_init (cfg, "rspamadm"); - setproctitle ("rspamdadm"); - /* Now read options and store everything till the first non-dash argument */ nargv = g_malloc0 (sizeof (gchar *) * (argc + 1)); nargv[0] = g_strdup (argv[0]); @@ -387,19 +368,52 @@ main (gint argc, gchar **argv, gchar **env) g_option_group_add_entries (og, entries); g_option_context_set_summary (context, "Summary:\n Rspamd administration utility version " - RVERSION - "\n Release id: " - RID); + RVERSION + "\n Release id: " + RID); g_option_context_set_main_group (context, og); targv = nargv; targc = nargc; + if (!g_option_context_parse (context, &targc, &targv, &error)) { fprintf (stderr, "option parsing failed: %s\n", error->message); g_error_free (error); exit (1); } + /* Setup logger */ + if (verbose) { + cfg->log_level = G_LOG_LEVEL_DEBUG; + } + else { + cfg->log_level = G_LOG_LEVEL_MESSAGE; + } + + cfg->log_type = RSPAMD_LOG_CONSOLE; + /* Avoid timestamps printing */ + cfg->log_flags = RSPAMD_LOG_FLAG_RSPAMADM; + rspamd_set_logger (cfg, process_quark, &rspamd_main->logger, + rspamd_main->server_pool); + (void) rspamd_log_open (rspamd_main->logger); + g_log_set_default_handler (rspamd_glib_log_function, rspamd_main->logger); + g_set_printerr_handler (rspamd_glib_printerr_function); + rspamd_config_post_load (cfg, + RSPAMD_CONFIG_INIT_LIBS|RSPAMD_CONFIG_INIT_URL|RSPAMD_CONFIG_INIT_NO_TLD); + + pworker = &workers[0]; + while (*pworker) { + /* Init string quarks */ + (void) g_quark_from_static_string ((*pworker)->name); + pworker++; + } + + cfg->compiled_modules = modules; + cfg->compiled_workers = workers; + + gperf_profiler_init (cfg, "rspamadm"); + setproctitle ("rspamdadm"); + L = cfg->lua_state; rspamd_lua_set_path (L, NULL, ucl_vars); rspamd_lua_set_globals (cfg, L, ucl_vars); diff --git a/src/rspamadm/rspamadm.h b/src/rspamadm/rspamadm.h index 3d9799dd5..02ecb2f47 100644 --- a/src/rspamadm/rspamadm.h +++ b/src/rspamadm/rspamadm.h @@ -57,6 +57,7 @@ void rspamadm_fill_lua_commands (lua_State *L, GPtrArray *dest); gboolean rspamadm_execute_lua_ucl_subr (gpointer L, gint argc, gchar **argv, const ucl_object_t *res, - const gchar *script_name); + const gchar *script_name, + gboolean rspamadm_subcommand); #endif diff --git a/src/rspamadm/stat_convert.c b/src/rspamadm/stat_convert.c index ef17194b0..acbe11550 100644 --- a/src/rspamadm/stat_convert.c +++ b/src/rspamadm/stat_convert.c @@ -224,11 +224,11 @@ rspamadm_statconvert (gint argc, gchar **argv, const struct rspamadm_command *cm ucl_object_insert_key (obj, redis, "redis", 0, false); ucl_object_insert_key (redis, ucl_object_fromstring (redis_host), - "host", 0, false); + "servers", 0, false); if (redis_db) { ucl_object_insert_key (redis, ucl_object_fromstring (redis_db), - "db", 0, false); + "dbname", 0, false); } if (redis_password) { @@ -251,7 +251,8 @@ rspamadm_statconvert (gint argc, gchar **argv, const struct rspamadm_command *cm argc, argv, obj, - "stat_convert"); + "stat_convert", + TRUE); ucl_object_unref (obj); } diff --git a/src/rspamd.c b/src/rspamd.c index 643d249b1..9339e0a3f 100644 --- a/src/rspamd.c +++ b/src/rspamd.c @@ -58,6 +58,8 @@ #include <openssl/evp.h> #endif +#include "sqlite3.h" + /* 2 seconds to fork new process in place of dead one */ #define SOFT_FORK_TIME 2 @@ -277,8 +279,6 @@ reread_config (struct rspamd_main *rspamd_main) rspamd_symbols_cache_save (rspamd_main->cfg->cache); tmp_cfg = rspamd_config_new (RSPAMD_CONFIG_INIT_DEFAULT); - g_hash_table_unref (tmp_cfg->c_modules); - tmp_cfg->c_modules = g_hash_table_ref (rspamd_main->cfg->c_modules); tmp_cfg->libs_ctx = rspamd_main->cfg->libs_ctx; REF_RETAIN (tmp_cfg->libs_ctx); cfg_file = rspamd_mempool_strdup (tmp_cfg->cfg_pool, @@ -289,8 +289,9 @@ reread_config (struct rspamd_main *rspamd_main) rspamd_main->cfg = tmp_cfg; if (!load_rspamd_config (rspamd_main, tmp_cfg, TRUE, - RSPAMD_CONFIG_INIT_VALIDATE|RSPAMD_CONFIG_INIT_SYMCACHE, - TRUE)) { + RSPAMD_CONFIG_INIT_VALIDATE|RSPAMD_CONFIG_INIT_SYMCACHE| + RSPAMD_CONFIG_INIT_LIBS|RSPAMD_CONFIG_INIT_URL, + TRUE)) { rspamd_main->cfg = old_cfg; rspamd_log_close_priv (rspamd_main->logger, rspamd_main->workers_uid, @@ -307,6 +308,7 @@ reread_config (struct rspamd_main *rspamd_main) msg_info_main ("replacing config"); REF_RELEASE (old_cfg); msg_info_main ("config has been reread successfully"); + rspamd_map_preload (rspamd_main->cfg); } } @@ -361,12 +363,15 @@ create_listen_socket (GPtrArray *addrs, guint cnt, g_ptr_array_sort (addrs, rspamd_inet_address_compare_ptr); for (i = 0; i < cnt; i ++) { + /* + * Copy address to avoid reload issues + */ if (listen_type & RSPAMD_WORKER_SOCKET_TCP) { fd = rspamd_inet_address_listen (g_ptr_array_index (addrs, i), SOCK_STREAM, TRUE); if (fd != -1) { ls = g_malloc0 (sizeof (*ls)); - ls->addr = g_ptr_array_index (addrs, i); + ls->addr = rspamd_inet_address_copy (g_ptr_array_index (addrs, i)); ls->fd = fd; ls->type = RSPAMD_WORKER_SOCKET_TCP; result = g_list_prepend (result, ls); @@ -377,7 +382,7 @@ create_listen_socket (GPtrArray *addrs, guint cnt, SOCK_DGRAM, TRUE); if (fd != -1) { ls = g_malloc0 (sizeof (*ls)); - ls->addr = g_ptr_array_index (addrs, i); + ls->addr = rspamd_inet_address_copy (g_ptr_array_index (addrs, i)); ls->fd = fd; ls->type = RSPAMD_WORKER_SOCKET_UDP; result = g_list_prepend (result, ls); @@ -750,11 +755,17 @@ wait_for_workers (gpointer key, gpointer value, gpointer unused) nowait ? "with no result available" : (WTERMSIG (res) == SIGKILL ? "hardly" : "softly")); if (w->srv_pipe[0] != -1) { + /* Ugly workaround */ + if (w->tmp_data) { + g_free (w->tmp_data); + } event_del (&w->srv_ev); } + if (w->finish_actions) { g_ptr_array_free (w->finish_actions, TRUE); } + REF_RELEASE (w->cf); g_free (w); @@ -960,7 +971,6 @@ rspamd_hup_handler (gint signo, short what, gpointer arg) RVERSION " is restarting"); g_hash_table_foreach (rspamd_main->workers, kill_old_workers, NULL); - rspamd_map_remove_all (rspamd_main->cfg); rspamd_log_close_priv (rspamd_main->logger, rspamd_main->workers_uid, rspamd_main->workers_gid); @@ -1056,6 +1066,10 @@ rspamd_cld_handler (gint signo, short what, gpointer arg) } if (cur->srv_pipe[0] != -1) { + /* Ugly workaround */ + if (cur->tmp_data) { + g_free (cur->tmp_data); + } event_del (&cur->srv_ev); } @@ -1168,7 +1182,7 @@ main (gint argc, gchar **argv, gchar **env) struct event term_ev, int_ev, cld_ev, hup_ev, usr1_ev, control_ev; struct timeval term_tv; struct rspamd_main *rspamd_main; - gboolean skip_pid = FALSE; + gboolean skip_pid = FALSE, valgrind_mode = FALSE; #if ((GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION <= 30)) g_thread_init (NULL); @@ -1184,8 +1198,12 @@ main (gint argc, gchar **argv, gchar **env) rspamd_spair_equal, g_free, rspamd_spair_close); rspamd_main->start_mtx = rspamd_mempool_get_mutex (rspamd_main->server_pool); + if (getenv ("VALGRIND") != NULL) { + valgrind_mode = TRUE; + } + #ifndef HAVE_SETPROCTITLE - init_title (argc, argv, env); + init_title (rspamd_main, argc, argv, env); #endif rspamd_main->cfg->libs_ctx = rspamd_init_libs (); @@ -1296,6 +1314,8 @@ main (gint argc, gchar **argv, gchar **env) return res ? EXIT_SUCCESS : EXIT_FAILURE; } + sqlite3_initialize (); + /* Load config */ if (!load_rspamd_config (rspamd_main, rspamd_main->cfg, TRUE, RSPAMD_CONFIG_LOAD_ALL, FALSE)) { @@ -1347,6 +1367,10 @@ main (gint argc, gchar **argv, gchar **env) rspamd_main->pid = getpid (); rspamd_main->type = type; + if (!valgrind_mode) { + rspamd_set_crash_handler (rspamd_main); + } + /* Ignore SIGPIPE as we handle write errors manually */ sigemptyset (&sigpipe_act.sa_mask); sigaddset (&sigpipe_act.sa_mask, SIGPIPE); @@ -1461,7 +1485,7 @@ main (gint argc, gchar **argv, gchar **env) close (control_fd); } - if (getenv ("VALGRIND") != NULL) { + if (valgrind_mode) { /* Special case if we are likely running with valgrind */ term_attempts = TERMINATION_ATTEMPTS * 10; } @@ -1495,6 +1519,7 @@ main (gint argc, gchar **argv, gchar **env) rspamd_log_close (rspamd_main->logger); REF_RELEASE (rspamd_main->cfg); g_hash_table_unref (rspamd_main->spairs); + g_hash_table_unref (rspamd_main->workers); rspamd_mempool_delete (rspamd_main->server_pool); if (!skip_pid) { @@ -1503,6 +1528,11 @@ main (gint argc, gchar **argv, gchar **env) g_free (rspamd_main); event_base_free (ev_base); + sqlite3_shutdown (); + + if (control_addr) { + rspamd_inet_address_free (control_addr); + } return (res); } diff --git a/src/rspamd.h b/src/rspamd.h index 1365a4b23..409c051b3 100644 --- a/src/rspamd.h +++ b/src/rspamd.h @@ -86,6 +86,7 @@ struct rspamd_worker { main process. [0] - main, [1] - worker */ struct event srv_ev; /**< used by main for read workers' requests */ gpointer control_data; /**< used by control protocol to handle commands */ + gpointer tmp_data; /**< used to avoid race condition to deal with control messages */ GPtrArray *finish_actions; /**< called when worker is terminated */ }; @@ -190,6 +191,7 @@ typedef struct module_s { guint module_version; guint64 rspamd_version; const gchar *rspamd_features; + guint ctx_offset; } module_t; enum rspamd_worker_socket_type { @@ -322,6 +324,7 @@ struct rspamd_external_libs_ctx { struct rspamd_cryptobox_library_ctx *crypto_ctx; struct ottery_config *ottery_cfg; SSL_CTX *ssl_ctx; + SSL_CTX *ssl_ctx_noverify; struct zstd_dictionary *in_dict; struct zstd_dictionary *out_dict; void *out_zstream; diff --git a/src/rspamd_proxy.c b/src/rspamd_proxy.c index d94ab6455..4af5ee5f4 100644 --- a/src/rspamd_proxy.c +++ b/src/rspamd_proxy.c @@ -1308,7 +1308,7 @@ proxy_backend_mirror_error_handler (struct rspamd_http_connection *conn, GError bk_conn->err = rspamd_mempool_strdup (session->pool, err->message); } - rspamd_upstream_fail (bk_conn->up); + rspamd_upstream_fail (bk_conn->up, FALSE); proxy_backend_close_connection (bk_conn); REF_RELEASE (bk_conn->s); @@ -1384,7 +1384,7 @@ proxy_open_mirror_connections (struct rspamd_proxy_session *session) if (bk_conn->backend_sock == -1) { msg_err_session ("cannot connect upstream for %s", m->name); - rspamd_upstream_fail (bk_conn->up); + rspamd_upstream_fail (bk_conn->up, TRUE); continue; } @@ -1499,13 +1499,13 @@ proxy_backend_master_error_handler (struct rspamd_http_connection *conn, GError struct rspamd_proxy_session *session; session = bk_conn->s; - msg_info_session ("abnormally closing connection from backend: %s, error: %s," + msg_info_session ("abnormally closing connection from backend: %s, error: %e," " retries left: %d", rspamd_inet_address_to_string (rspamd_upstream_addr (session->master_conn->up)), - err->message, + err, session->ctx->max_retries - session->retries); session->retries ++; - rspamd_upstream_fail (bk_conn->up); + rspamd_upstream_fail (bk_conn->up, FALSE); proxy_backend_close_connection (session->master_conn); if (session->ctx->max_retries && @@ -1810,7 +1810,7 @@ retry: host ? hostbuf : "default", rspamd_inet_address_to_string (rspamd_upstream_addr ( session->master_conn->up))); - rspamd_upstream_fail (session->master_conn->up); + rspamd_upstream_fail (session->master_conn->up, TRUE); session->retries ++; goto retry; } @@ -1941,7 +1941,7 @@ proxy_client_finish_handler (struct rspamd_http_connection *conn, /* Reset spamc legacy */ if (msg->method >= HTTP_SYMBOLS) { - msg->method = HTTP_GET; + msg->method = HTTP_POST; if (msg->flags & RSPAMD_HTTP_FLAG_SPAMC) { session->legacy_support = LEGACY_SUPPORT_SPAMC; @@ -2165,7 +2165,7 @@ start_rspamd_proxy (struct rspamd_worker *worker) { ctx->ev_base, worker->srv->cfg); double_to_tv (ctx->timeout, &ctx->io_tv); - rspamd_map_watch (worker->srv->cfg, ctx->ev_base, ctx->resolver, 0); + rspamd_map_watch (worker->srv->cfg, ctx->ev_base, ctx->resolver, worker, 0); rspamd_upstreams_library_config (worker->srv->cfg, ctx->cfg->ups_ctx, ctx->ev_base, ctx->resolver->r); diff --git a/src/worker.c b/src/worker.c index 6b02b5753..f26a86ff7 100644 --- a/src/worker.c +++ b/src/worker.c @@ -676,7 +676,7 @@ start_worker (struct rspamd_worker *worker) ctx->resolver = dns_resolver_init (worker->srv->logger, ctx->ev_base, worker->srv->cfg); - rspamd_map_watch (worker->srv->cfg, ctx->ev_base, ctx->resolver, 0); + rspamd_map_watch (worker->srv->cfg, ctx->ev_base, ctx->resolver, worker, 0); rspamd_upstreams_library_config (worker->srv->cfg, ctx->cfg->ups_ctx, ctx->ev_base, ctx->resolver->r); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 71a412d84..f2d846525 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -33,9 +33,7 @@ TARGET_LINK_LIBRARIES(rspamd-test ${RSPAMD_REQUIRED_LIBRARIES}) IF (ENABLE_SNOWBALL MATCHES "ON") TARGET_LINK_LIBRARIES(rspamd-test stemmer) ENDIF() -IF(ENABLE_HIREDIS MATCHES "ON") - TARGET_LINK_LIBRARIES(rspamd-test rspamd-hiredis) -ENDIF() +TARGET_LINK_LIBRARIES(rspamd-test rspamd-hiredis) IF (ENABLE_HYPERSCAN MATCHES "ON") TARGET_LINK_LIBRARIES(rspamd-test hs) ENDIF() diff --git a/test/functional/cases/100_general.robot b/test/functional/cases/100_general.robot index 31d82c4f8..0e5a89c15 100644 --- a/test/functional/cases/100_general.robot +++ b/test/functional/cases/100_general.robot @@ -9,6 +9,7 @@ Variables ${TESTDIR}/lib/vars.py ${CONFIG} ${TESTDIR}/configs/trivial.conf ${GTUBE} ${TESTDIR}/messages/gtube.eml ${RSPAMD_SCOPE} Suite +${URL_TLD} ${TESTDIR}/../lua/unit/test_tld.dat *** Test Cases *** GTUBE @@ -41,8 +42,15 @@ GTUBE - RSPAMC Follow Rspamd Log Should Contain ${result} GTUBE -EMAILS DETECTION 1 - ${result} = Scan Message With Rspamc ${TESTDIR}/messages/emails1.eml - Check Rspamc ${result} "jim@example.net" - Should Contain ${result.stdout} "bob@example.net" - Should Contain ${result.stdout} "rupert@example.net" +# Broken +#EMAILS DETECTION 1 +# ${result} = Scan Message With Rspamc ${TESTDIR}/messages/emails1.eml +# Check Rspamc ${result} "jim@example.net" +# Should Contain ${result.stdout} "bob@example.net" +# Should Contain ${result.stdout} "rupert@example.net" + +EMAILS DETECTION ZEROFONT + ${result} = Scan File ${LOCAL_ADDR} ${PORT_NORMAL} ${TESTDIR}/messages/zerofont.eml + Follow Rspamd Log + Should Contain ${result} MANY_INVISIBLE_PARTS + Should Contain ${result} ZERO_FONT
\ No newline at end of file diff --git a/test/functional/cases/102_multimap.robot b/test/functional/cases/102_multimap.robot index c46fff39b..7aac43e4e 100644 --- a/test/functional/cases/102_multimap.robot +++ b/test/functional/cases/102_multimap.robot @@ -133,6 +133,14 @@ MAP - HOSTNAME MISS ${result} = Scan Message With Rspamc ${MESSAGE} --ip 127.0.0.1 --hostname rspamd.com Check Rspamc ${result} HOSTNAME_MAP inverse=1 +MAP - TOP + ${result} = Scan Message With Rspamc ${MESSAGE} --ip 127.0.0.1 --hostname example.com.au + Check Rspamc ${result} HOSTNAME_TOP_MAP + +MAP - TOP MISS + ${result} = Scan Message With Rspamc ${MESSAGE} --ip 127.0.0.1 --hostname example.com.bg + Check Rspamc ${result} HOSTNAME_TOP_MAP inverse=1 + MAP - CDB - HOSTNAME ${result} = Scan Message With Rspamc ${MESSAGE} --ip 127.0.0.1 --hostname example.com Check Rspamc ${result} CDB_HOSTNAME diff --git a/test/functional/cases/105_mimetypes.robot b/test/functional/cases/105_mimetypes.robot index 895255194..3c1a03930 100644 --- a/test/functional/cases/105_mimetypes.robot +++ b/test/functional/cases/105_mimetypes.robot @@ -45,6 +45,10 @@ Multipart Archive Extension ${result} = Scan Message With Rspamc ${TESTDIR}/messages/f.zip.001.eml Should Not Contain ${result.stdout} MIME_ARCHIVE_IN_ARCHIVE +Empty text part should not be treat as html + ${result} = Scan Message With Rspamc ${TESTDIR}/messages/empty-plain-text.eml + Should Not Contain ${result.stdout} FORGED_OUTLOOK_HTML + *** Keywords *** MIMETypes Setup ${PLUGIN_CONFIG} = Get File ${TESTDIR}/configs/mime_types.conf diff --git a/test/functional/cases/120_fuzzy/replication.robot b/test/functional/cases/120_fuzzy/replication.robot deleted file mode 100644 index cb7d42533..000000000 --- a/test/functional/cases/120_fuzzy/replication.robot +++ /dev/null @@ -1,84 +0,0 @@ -*** Settings *** -Suite Setup Replication Setup -Suite Teardown Replication Teardown -Resource lib.robot -Library ${TESTDIR}/lib/rspamd.py -Resource ${TESTDIR}/lib/rspamd.robot -Variables ${TESTDIR}/lib/vars.py - -*** Variables *** -${MESSAGE} @{MESSAGES}[0] - -*** Test Cases *** -Fuzzy Add And Check - Set Suite Variable ${RSPAMD_FUZZY_ADD_${MESSAGE}} 0 - ${result} = Run Rspamc -h ${LOCAL_ADDR}:${PORT_CONTROLLER} -w 10 -f - ... ${FLAG1_NUMBER} fuzzy_add ${MESSAGE} - Custom Follow Rspamd Log ${MASTER_TMPDIR}/rspamd.log ${MASTER_LOGPOS} MASTER_LOGPOS Suite - Custom Follow Rspamd Log ${SLAVE_TMPDIR}/rspamd.log ${SLAVE_LOGPOS} SLAVE_LOGPOS Suite - Run Keyword If ${result.rc} != 0 Log ${result.stderr} - Should Contain ${result.stdout} success = true - Should Be Equal As Integers ${result.rc} 0 - Sync Fuzzy Storage ${MASTER_TMPDIR} ${MASTER_LOGPOS} MASTER_LOGPOS Suite - Sync Fuzzy Storage ${SLAVE_TMPDIR} ${SLAVE_LOGPOS} SLAVE_LOGPOS Suite - ${result} = Scan Message With Rspamc ${MESSAGE} - Custom Follow Rspamd Log ${MASTER_TMPDIR}/rspamd.log ${MASTER_LOGPOS} MASTER_LOGPOS Suite - Custom Follow Rspamd Log ${SLAVE_TMPDIR}/rspamd.log ${SLAVE_LOGPOS} SLAVE_LOGPOS Suite - Run Keyword If ${result.rc} != 0 Log ${result.stderr} - Should Contain ${result.stdout} ${FLAG1_SYMBOL} - Should Be Equal As Integers ${result.rc} 0 - Set Suite Variable ${RSPAMD_FUZZY_ADD_${MESSAGE}} 1 - -Fuzzy Check Slave - Run Keyword If ${RSPAMD_FUZZY_ADD_${MESSAGE}} == 0 Fail "Fuzzy Add was not run" - ${result} = Run Rspamc -h ${LOCAL_ADDR}:${PORT_NORMAL_SLAVE} ${MESSAGE} - Custom Follow Rspamd Log ${SLAVE_TMPDIR}/rspamd.log ${SLAVE_LOGPOS} SLAVE_LOGPOS Suite - Run Keyword If ${result.rc} != 0 Log ${result.stderr} - Should Contain ${result.stdout} ${FLAG1_SYMBOL} - Should Be Equal As Integers ${result.rc} 0 - -*** Keywords *** -Replication Setup - ${tmp_fuzzy} = Set Variable ${PORT_FUZZY} - ${tmp_normal} = Set Variable ${PORT_NORMAL} - ${tmp_controller} = Set Variable ${PORT_CONTROLLER} - Set Suite Variable ${PORT_FUZZY} ${PORT_FUZZY_SLAVE} - Set Suite Variable ${PORT_NORMAL} ${PORT_NORMAL_SLAVE} - Set Suite Variable ${PORT_CONTROLLER} ${PORT_CONTROLLER_SLAVE} - ${algorithm} = Set Variable mumhash - ${worker_settings_tmpl} = Get File ${TESTDIR}/configs/fuzzy_slave_worker.conf - ${worker_settings} = Replace Variables ${worker_settings_tmpl} - ${tmp_include1} = Make Temporary File - Set Suite Variable ${TMP_INCLUDE1} ${tmp_include1} - Create File ${tmp_include1} ${worker_settings} - ${worker_settings} = Set Variable .include ${tmp_include1} - ${check_settings} = Set Variable ${EMPTY} - Set Suite Variable ${SETTINGS_FUZZY_WORKER} ${worker_settings} - Set Suite Variable ${SETTINGS_FUZZY_CHECK} ${check_settings} - Set Suite Variable ${ALGORITHM} ${algorithm} - &{d} = Run Rspamd CONFIG=${TESTDIR}/configs/fuzzy.conf - Set Suite Variable ${SLAVE_LOGPOS} &{d}[RSPAMD_LOGPOS] - Set Suite Variable ${SLAVE_PID} &{d}[RSPAMD_PID] - Set Suite Variable ${SLAVE_TMPDIR} &{d}[TMPDIR] - Set Suite Variable ${PORT_FUZZY} ${tmp_fuzzy} - Set Suite Variable ${PORT_NORMAL} ${tmp_normal} - Set Suite Variable ${PORT_CONTROLLER} ${tmp_controller} - ${worker_settings_tmpl} = Get File ${TESTDIR}/configs/fuzzy_master_worker.conf - ${worker_settings} = Replace Variables ${worker_settings_tmpl} - ${tmp_include2} = Make Temporary File - Set Suite Variable ${TMP_INCLUDE2} ${tmp_include2} - Create File ${tmp_include2} ${worker_settings} - ${worker_settings} = Set Variable .include ${tmp_include2} - Set Suite Variable ${SETTINGS_FUZZY_WORKER} ${worker_settings} - &{d} = Run Rspamd CONFIG=${TESTDIR}/configs/fuzzy.conf - Set Suite Variable ${MASTER_LOGPOS} &{d}[RSPAMD_LOGPOS] - Set Suite Variable ${MASTER_PID} &{d}[RSPAMD_PID] - Set Suite Variable ${MASTER_TMPDIR} &{d}[TMPDIR] - -Replication Teardown - Shutdown Process With Children ${MASTER_PID} - Shutdown Process With Children ${SLAVE_PID} - Cleanup Temporary Directory ${MASTER_TMPDIR} - Cleanup Temporary Directory ${SLAVE_TMPDIR} - Remove File ${TMP_INCLUDE1} - Remove File ${TMP_INCLUDE2} diff --git a/test/functional/cases/130_dkim.robot b/test/functional/cases/130_dkim.robot index 1bb08e639..ad0b27ac4 100644 --- a/test/functional/cases/130_dkim.robot +++ b/test/functional/cases/130_dkim.robot @@ -22,3 +22,11 @@ DKIM Self Verify Run Keyword If ${RAN_SIGNTEST} == 0 Fail "Sign test was not run" ${result} = Scan Message With Rspamc ${SIGNED_MESSAGE} Check Rspamc ${result} R_DKIM_ALLOW + +DKIM Verify ED25519 PASS + ${result} = Scan Message With Rspamc ${TESTDIR}/messages/ed25519.eml + Check Rspamc ${result} R_DKIM_ALLOW + +DKIM Verify ED25519 REJECT + ${result} = Scan Message With Rspamc ${TESTDIR}/messages/ed25519-broken.eml + Check Rspamc ${result} R_DKIM_REJECT
\ No newline at end of file diff --git a/test/functional/cases/131_dkim_signing/001_simple.robot b/test/functional/cases/131_dkim_signing/001_simple.robot index 66bcffe37..d5bd56841 100644 --- a/test/functional/cases/131_dkim_signing/001_simple.robot +++ b/test/functional/cases/131_dkim_signing/001_simple.robot @@ -8,6 +8,7 @@ Variables ${TESTDIR}/lib/vars.py *** Variables *** ${CONFIG} ${TESTDIR}/configs/plugins.conf ${MESSAGE} ${TESTDIR}/messages/dmarc/fail_none.eml +${MESSAGE_FAIL} ${TESTDIR}/messages/dmarc/fail_none1.eml ${REDIS_SCOPE} Suite ${RSPAMD_SCOPE} Suite ${URL_TLD} ${TESTDIR}/../lua/unit/test_tld.dat @@ -23,6 +24,11 @@ TEST NOT SIGNED - USERNAME WRONG DOMAIN Check Rspamc ${result} DKIM-Signature: inverse=1 Should Not Contain ${result.stdout} DKIM_SIGNED (1.00) +TEST NOT SIGNED - USERNAME WRONG PUBKEY + ${result} = Scan Message With Rspamc ${MESSAGE_FAIL} -u bob@invalid.za.org + Check Rspamc ${result} DKIM-Signature: inverse=1 + Should Not Contain ${result.stdout} DKIM_SIGNED (1.00) + *** Keywords *** DKIM Signing Setup ${PLUGIN_CONFIG} = Get File ${TESTDIR}/configs/dkim_signing/simple.conf diff --git a/test/functional/cases/140_proxy.robot b/test/functional/cases/140_proxy.robot index 7b29f188a..a4fe9d7ca 100644 --- a/test/functional/cases/140_proxy.robot +++ b/test/functional/cases/140_proxy.robot @@ -23,13 +23,13 @@ SPAMC ${result} = Spamc ${LOCAL_ADDR} ${PORT_PROXY} ${MESSAGE} Custom Follow Rspamd Log ${PROXY_TMPDIR}/rspamd.log ${PROXY_LOGPOS} PROXY_LOGPOS Suite Custom Follow Rspamd Log ${SLAVE_TMPDIR}/rspamd.log ${SLAVE_LOGPOS} SLAVE_LOGPOS Suite - Should Contain ${result} SIMPLE_TEST + Should Contain ${result} SPAMD/1.1 0 EX_OK RSPAMC Legacy Protocol ${result} = Rspamc ${LOCAL_ADDR} ${PORT_PROXY} ${MESSAGE} Custom Follow Rspamd Log ${PROXY_TMPDIR}/rspamd.log ${PROXY_LOGPOS} PROXY_LOGPOS Suite Custom Follow Rspamd Log ${SLAVE_TMPDIR}/rspamd.log ${SLAVE_LOGPOS} SLAVE_LOGPOS Suite - Should Contain ${result} SIMPLE_TEST + Should Contain ${result} RSPAMD/1.3 0 EX_OK *** Keywords *** Proxy Setup diff --git a/test/functional/configs/dkim.conf b/test/functional/configs/dkim.conf index b10cff0c4..4ac7bf6b5 100644 --- a/test/functional/configs/dkim.conf +++ b/test/functional/configs/dkim.conf @@ -1,33 +1,53 @@ options = { - filters = ["dkim"] - pidfile = "${TMPDIR}/rspamd.pid" - dns { - retransmits = 10; - timeout = 2s; - } + filters = ["dkim"] + pidfile = "${TMPDIR}/rspamd.pid" + dns { + retransmits = 10; + timeout = 2s; + fake_records = [{ # ed25519 + name = "test._domainkey.example.com"; + type = txt; + replies = ["k=ed25519; p=yi50DjK5O9pqbFpNHklsv9lqaS0ArSYu02qp1S0DW1Y="]; + }, + { + name = "brisbane._domainkey.football.example.com"; + type = txt; + replies = ["v=DKIM1; k=ed25519; p=11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo="]; + }, + { + name = "test._domainkey.football.example.com"; + type = txt; + replies = ["v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDkHlOQoBTzWRiGs5V6NpP3idY6Wk08a5qhdR6wy5bdOKb2jLQiY/J16JYi0Qvx/byYzCNb3W91y3FutACDfzwQ/BC/e/8uBsCR+yz1Lxj+PL6lHvqMKrM3rG4hstT5QjvHO9PzoxZyVYLzBfO2EeC3Ip3G+2kryOTIKT+l/K4w3QIDAQAB"], + }, + { + name = "dkim._domainkey.cacophony.za.org", + type = "txt"; + replies = ["v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDXtxBE5IiNRMcq2/lc2zErfdCvDFyQNBnMjbOjBQrPST2k4fdGbtpe5Iu5uS01Met+dAEf94XL8I0hwmYw+n70PP834zfJGi2egwGqrakpaWsCDPvIJZLkxJCJKQRA/zrQ622uEXdvYixVbsEGVw7U4wAGSmT5rU2eU1y63AlOlQIDAQAB"]; + }]; + } } logging = { - type = "file", - level = "debug" - filename = "${TMPDIR}/rspamd.log" + type = "file", + level = "debug" + filename = "${TMPDIR}/rspamd.log" } metric = { - name = "default", - actions = { - reject = 100500, - } - unknown_weight = 1 + name = "default", + actions = { + reject = 100500, + } + unknown_weight = 1 } worker { - type = normal - bind_socket = ${LOCAL_ADDR}:${PORT_NORMAL} - count = 1 - keypair { - pubkey = "${KEY_PUB1}"; - privkey = "${KEY_PVT1}"; - } - task_timeout = 60s; + type = normal + bind_socket = ${LOCAL_ADDR}:${PORT_NORMAL} + count = 1 + keypair { + pubkey = "${KEY_PUB1}"; + privkey = "${KEY_PVT1}"; + } + task_timeout = 60s; } worker { diff --git a/test/functional/configs/dkim_signing/simple.conf b/test/functional/configs/dkim_signing/simple.conf index d233beff6..2302a0c4f 100644 --- a/test/functional/configs/dkim_signing/simple.conf +++ b/test/functional/configs/dkim_signing/simple.conf @@ -1,3 +1,5 @@ dkim_signing { path = "${TESTDIR}/configs/dkim.key"; + check_pubkey = true; + allow_pubkey_mismatch = false; } diff --git a/test/functional/configs/maps/top.list b/test/functional/configs/maps/top.list new file mode 100644 index 000000000..d8a152ed8 --- /dev/null +++ b/test/functional/configs/maps/top.list @@ -0,0 +1,2 @@ +au +#bg
\ No newline at end of file diff --git a/test/functional/configs/mime_types.conf b/test/functional/configs/mime_types.conf index 4aa1ac0ef..a16434b6a 100644 --- a/test/functional/configs/mime_types.conf +++ b/test/functional/configs/mime_types.conf @@ -1,5 +1,8 @@ mime_types { - file = "${TESTDIR}/../../../conf/mime_types.inc"; + file = [ + "https://maps.rspamd.com/rspamd/mime_types.inc.zst", + "fallback+file://${TESTDIR}/../../../conf/mime_types.inc" + ]; extension_map { html = "text/html"; txt [ diff --git a/test/functional/configs/multimap.conf b/test/functional/configs/multimap.conf index 98701794f..61bd310a8 100644 --- a/test/functional/configs/multimap.conf +++ b/test/functional/configs/multimap.conf @@ -58,6 +58,11 @@ multimap { type = "hostname"; map = "${TESTDIR}/configs/maps/domains.list"; } + HOSTNAME_TOP_MAP { + type = "hostname"; + filter = "top"; + map = "${TESTDIR}/configs/maps/top.list"; + } CDB_HOSTNAME { type = "hostname"; map = "cdb://${TESTDIR}/configs/maps/domains.cdb"; diff --git a/test/functional/configs/nginx.conf b/test/functional/configs/nginx.conf new file mode 100644 index 000000000..72472d16e --- /dev/null +++ b/test/functional/configs/nginx.conf @@ -0,0 +1,20 @@ +events { +} +worker_processes 1; +pid ${TMPDIR}/nginx.pid; +error_log ${TMPDIR}/error.log; +http { + default_type application/octet-stream; + sendfile on; + + server { + # no need for root privileges + listen ${NGINX_ADDR}:${NGINX_PORT}; + server_name localhost; + + location / { + root ${TMPDIR}; + autoindex on; + } + } +} diff --git a/test/functional/configs/plugins.conf b/test/functional/configs/plugins.conf index eb7971478..65141d5c0 100644 --- a/test/functional/configs/plugins.conf +++ b/test/functional/configs/plugins.conf @@ -1,31 +1,46 @@ options = { - filters = ["spf", "dkim", "regexp"] - url_tld = "${URL_TLD}" - pidfile = "${TMPDIR}/rspamd.pid" - lua_path = "${INSTALLROOT}/share/rspamd/lib/?.lua" - dns { - nameserver = ["8.8.8.8", "8.8.4.4"]; - retransmits = 10; - timeout = 2s; - } + filters = ["spf", "dkim", "regexp"] + url_tld = "${URL_TLD}" + pidfile = "${TMPDIR}/rspamd.pid" + lua_path = "${INSTALLROOT}/share/rspamd/lib/?.lua" + dns { + nameserver = ["8.8.8.8", "8.8.4.4"]; + retransmits = 10; + timeout = 2s; + fake_records = [{ # ed25519 + name = "test._domainkey.example.com"; + type = txt; + replies = ["k=ed25519; p=yi50DjK5O9pqbFpNHklsv9lqaS0ArSYu02qp1S0DW1Y="]; + }, + { + name = "dkim._domainkey.cacophony.za.org", + type = "txt"; + replies = ["v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDXtxBE5IiNRMcq2/lc2zErfdCvDFyQNBnMjbOjBQrPST2k4fdGbtpe5Iu5uS01Met+dAEf94XL8I0hwmYw+n70PP834zfJGi2egwGqrakpaWsCDPvIJZLkxJCJKQRA/zrQ622uEXdvYixVbsEGVw7U4wAGSmT5rU2eU1y63AlOlQIDAQAB"]; + }, + { + name = "dkim._domainkey.invalid.za.org", + type = "txt"; + replies = ["v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDEEXmNGQq7PUrr9Mg4UakTFHgXBCy2DOztkrZm+0OrVWtiRzGluxBkbOWTBwuU3/Yw97yTphBMQxzWFN603/f/KPAQcF/Lc1l+6kmIBBxNXjjGuOK/3PYKZVntUdKmqcQBYfnHdzH2Tohbuyx1a7xqnv6VSChqQrZU4CwkeT3+eQIDAQAB"]; + }]; + } } logging = { - type = "file", - level = "debug" - filename = "${TMPDIR}/rspamd.log" + type = "file", + level = "debug" + filename = "${TMPDIR}/rspamd.log" } metric = { - name = "default", - actions = { - reject = 100500, - } - unknown_weight = 1 + name = "default", + actions = { + reject = 100500, + } + unknown_weight = 1 } worker { - type = normal - bind_socket = ${LOCAL_ADDR}:${PORT_NORMAL} - count = 1 - task_timeout = 60s; + type = normal + bind_socket = ${LOCAL_ADDR}:${PORT_NORMAL} + count = 1 + task_timeout = 60s; } worker { type = controller diff --git a/test/functional/configs/trivial.conf b/test/functional/configs/trivial.conf index 4a16554ca..06aa194e3 100644 --- a/test/functional/configs/trivial.conf +++ b/test/functional/configs/trivial.conf @@ -1,10 +1,12 @@ options = { filters = ["spf", "dkim", "regexp"] url_tld = "${TESTDIR}/../lua/unit/test_tld.dat" - pidfile = "${TMPDIR}/rspamd.pid" + pidfile = "${TMPDIR}/rspamd.pid"; + lua_path = "${INSTALLROOT}/share/rspamd/lib/?.lua"; dns { - retransmits = 10; - timeout = 2s; + nameserver = ["8.8.8.8", "8.8.4.4"]; + retransmits = 10; + timeout = 2s; } } logging = { @@ -39,3 +41,9 @@ worker { secure_ip = ["127.0.0.1", "::1"]; stats_path = "${TMPDIR}/stats.ucl" } + +modules { + path = "${TESTDIR}/../../src/plugins/lua/" +} +lua = "${INSTALLROOT}/share/rspamd/rules/rspamd.lua" + diff --git a/test/functional/lib/rspamd.robot b/test/functional/lib/rspamd.robot index 6a36746a3..7b355f85b 100644 --- a/test/functional/lib/rspamd.robot +++ b/test/functional/lib/rspamd.robot @@ -108,9 +108,26 @@ Run Redis ${redis_log} = Get File ${TMPDIR}/redis.log Log ${redis_log} +Run Nginx + ${template} = Get File ${TESTDIR}/configs/nginx.conf + ${config} = Replace Variables ${template} + Create File ${TMPDIR}/nginx.conf ${config} + Log ${config} + ${result} = Run Process nginx -c ${TMPDIR}/nginx.conf + Run Keyword If ${result.rc} != 0 Log ${result.stderr} + Should Be Equal As Integers ${result.rc} 0 + Wait Until Keyword Succeeds 30 sec 1 sec Check Pidfile ${TMPDIR}/nginx.pid + Wait Until Keyword Succeeds 30 sec 1 sec TCP Connect ${NGINX_ADDR} ${NGINX_PORT} + ${NGINX_PID} = Get File ${TMPDIR}/nginx.pid + Run Keyword If '${NGINX_SCOPE}' == 'Test' Set Test Variable ${NGINX_PID} + ... ELSE IF '${NGINX_SCOPE}' == 'Suite' Set Suite Variable ${NGINX_PID} + ${nginx_log} = Get File ${TMPDIR}/nginx.log + Log ${nginx_log} + Run Rspamc [Arguments] @{args} ${result} = Run Process ${RSPAMC} -t 60 @{args} env:LD_LIBRARY_PATH=${TESTDIR}/../../contrib/aho-corasick + Log ${result.stdout} [Return] ${result} Run Rspamd @@ -138,7 +155,6 @@ Run Rspamd Wait Until Keyword Succeeds 30 sec 1 sec Check Pidfile ${tmpdir}/rspamd.pid ${rspamd_pid} = Get File ${tmpdir}/rspamd.pid Set To Dictionary ${d} RSPAMD_LOGPOS=${rspamd_logpos} RSPAMD_PID=${rspamd_pid} TMPDIR=${tmpdir} - Sleep 1s Give a moment to read maps [Return] &{d} Scan Message With Rspamc @@ -158,5 +174,5 @@ Sync Fuzzy Storage ... ELSE Run Process ${RSPAMADM} control -s @{vargs}[0]/rspamd.sock fuzzy_sync Log ${result.stdout} Run Keyword If $len == 0 Follow Rspamd Log - ... ELSE Custom Follow Rspamd Log @{vargs}[0]/rspamd.log @{vargs}[1] @{vargs}[2] @{vargs}[3] + ... ELSE Custom Follow Rspamd Log @{vargs}[0]/rspamd.log @{vargs}[1] @{vargs}[2] @{vargs}[3] Sleep 0.1s Try give fuzzy storage time to sync diff --git a/test/functional/lib/vars.py b/test/functional/lib/vars.py index 07ac9889d..e707480a8 100644 --- a/test/functional/lib/vars.py +++ b/test/functional/lib/vars.py @@ -17,6 +17,8 @@ PORT_FPROT = 56797 PORT_FPROT_DUPLICATE = 56798 REDIS_ADDR = u'127.0.0.1' REDIS_PORT = 56379 +NGINX_ADDR = u'127.0.0.1' +NGINX_PORT = 56380 RSPAMD_GROUP = 'nogroup' RSPAMD_USER = 'nobody' SOCK_DGRAM = socket.SOCK_DGRAM diff --git a/test/functional/messages/dmarc/fail_none1.eml b/test/functional/messages/dmarc/fail_none1.eml new file mode 100644 index 000000000..1579bb71f --- /dev/null +++ b/test/functional/messages/dmarc/fail_none1.eml @@ -0,0 +1,3 @@ +From: Rspamd <foo@invalid.za.org> + +hello diff --git a/test/functional/messages/ed25519-broken.eml b/test/functional/messages/ed25519-broken.eml new file mode 100644 index 000000000..85426fbcc --- /dev/null +++ b/test/functional/messages/ed25519-broken.eml @@ -0,0 +1,26 @@ +DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; + d=football.example.com; i=@football.example.com; + q=dns/txt; s=brisbane; t=1528637909; h=from : to : + subject : date : message-id : from : subject : date; + bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=; + b=/gCrinpcQOoIfuHNQIbq4pgh9kyIK3AQUdt9OdqQehSwhEIug4D11Bus + Fa3bT3FY5OsU7ZbnKELq+eXdp1Q1Dw== +DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; + d=football.example.com; i=@football.example.com; + q=dns/txt; s=test; t=1528637909; h=from : to : subject : + date : message-id : from : subject : date; + bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=; + b=F45dVWDfMbQDGHJFlXUNB2HKfbCeLRyhDXgFpEL8GwpsRe0IeIixNTe3 + DhCVlUrSjV4BwcVcOF6+FF3Zo9Rpo1tFOeS9mPYQTnGdaSGsgeefOsk2Jz + dA+L10TeYt9BgDfQNZtKdN1WO//KgIqXP7OdEFE4LjFYNcUxZQ4FADY+8= +From: Joe SixPack <joe@evil.example.com> +To: Suzie Q <suzie@shopping.example.net> +Subject: Is dinner ready? +Date: Fri, 11 Jul 2003 21:00:37 -0700 (PDT) +Message-ID: <20030712040037.46341.5F8J@football.example.com> + +Hi. + +We lost the game. Are you hungry yet? + +Joe. diff --git a/test/functional/messages/ed25519.eml b/test/functional/messages/ed25519.eml new file mode 100644 index 000000000..a3397f2d4 --- /dev/null +++ b/test/functional/messages/ed25519.eml @@ -0,0 +1,26 @@ +DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; + d=football.example.com; i=@football.example.com; + q=dns/txt; s=brisbane; t=1528637909; h=from : to : + subject : date : message-id : from : subject : date; + bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=; + b=/gCrinpcQOoIfuHNQIbq4pgh9kyIK3AQUdt9OdqQehSwhEIug4D11Bus + Fa3bT3FY5OsU7ZbnKELq+eXdp1Q1Dw== +DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; + d=football.example.com; i=@football.example.com; + q=dns/txt; s=test; t=1528637909; h=from : to : subject : + date : message-id : from : subject : date; + bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=; + b=F45dVWDfMbQDGHJFlXUNB2HKfbCeLRyhDXgFpEL8GwpsRe0IeIixNTe3 + DhCVlUrSjV4BwcVcOF6+FF3Zo9Rpo1tFOeS9mPYQTnGdaSGsgeefOsk2Jz + dA+L10TeYt9BgDfQNZtKdN1WO//KgIqXP7OdEFE4LjFYNcUxZQ4FADY+8= +From: Joe SixPack <joe@football.example.com> +To: Suzie Q <suzie@shopping.example.net> +Subject: Is dinner ready? +Date: Fri, 11 Jul 2003 21:00:37 -0700 (PDT) +Message-ID: <20030712040037.46341.5F8J@football.example.com> + +Hi. + +We lost the game. Are you hungry yet? + +Joe. diff --git a/test/functional/messages/empty-plain-text.eml b/test/functional/messages/empty-plain-text.eml new file mode 100644 index 000000000..2c4fa3e90 --- /dev/null +++ b/test/functional/messages/empty-plain-text.eml @@ -0,0 +1,14 @@ +Return-Path: test@test.com +From: TEST <test@test.com> +MIME-Version: 1.0 +X-Priority: 1 (Highest) +X-MSMail-Priority: High +X-Mailer: Microsoft Outlook 16.0 +Importance: High +Date: Mon, 23 Jul 2018 16:24:13 +0200 +Message-ID: <d9946a191e0c97733a86424c48e65eca@test.com> +Subject: Test Subject +To: Me <me@me.me> +Content-Type: text/plain; charset="UTF-8" + + diff --git a/test/functional/messages/gtube.eml b/test/functional/messages/gtube.eml index cb7363b75..bb1474679 100644 --- a/test/functional/messages/gtube.eml +++ b/test/functional/messages/gtube.eml @@ -5,7 +5,14 @@ From: Sender <sender@example.net> To: Recipient <recipient@example.net> Precedence: junk MIME-Version: 1.0 -Content-Type: text/plain; charset=us-ascii +Content-Type: multipart/mixed; + boundary="--==_mimepart_5b60ba5e43e33_7d8f3fa857ebe62053695f"; + charset=UTF-8 +Content-Transfer-Encoding: 7bit + +----==_mimepart_5b60ba5e43e33_7d8f3fa857ebe62053695f +Content-Type: text/plain; + charset=UTF-8 Content-Transfer-Encoding: 7bit This is the GTUBE, the @@ -23,3 +30,27 @@ characters (in upper case and with no white spaces and line breaks): XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X You should send this test mail from an account outside of your network. + +----==_mimepart_5b60ba5e43e33_7d8f3fa857ebe62053695f +Content-Type: text/plain; + charset=UTF-8 +Content-Transfer-Encoding: 7bit + +XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X + +----==_mimepart_5b60ba5e43e33_7d8f3fa857ebe62053695f +Content-Type: text/html; + charset=UTF-8 +Content-Transfer-Encoding: 7bit + +<html> +XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X +XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X +XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X +XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X +XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X +XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X +XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X +</html> + +----==_mimepart_5b60ba5e43e33_7d8f3fa857ebe62053695f--
\ No newline at end of file diff --git a/test/functional/messages/zerofont.eml b/test/functional/messages/zerofont.eml new file mode 100644 index 000000000..79fa5ede4 --- /dev/null +++ b/test/functional/messages/zerofont.eml @@ -0,0 +1,17 @@ +From: foobar@example.com +Content-Type: text/html + +<!doctype html> +<html lang="en-US" dir="ltr"> + <head> + <meta charset="utf-8"> + <meta http-equiv="Content-Security-Policy-Report-Only" content="script-src 'unsafe-inline'; img-src http: https: data: blob:; style-src 'unsafe-inline'; child-src 'none'; object-src 'none'; report-uri https://tiles.services.mozilla.com/v4/links/activity-stream/csp"> + <title>New Tab</title> + <link rel="icon" type="image/png" href="chrome://branding/content/icon32.png"/> + <link rel="stylesheet" href="chrome://browser/content/contentSearchUI.css" /> + <link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" /> + </head> + <body class="activity-stream"> + <div>fi<span style="FONT-SIZE: 0px">le </span>sh<span style="FONT-SIZE: 0px">aring </span></div> + </body> +</html> diff --git a/test/lua/unit/addr.lua b/test/lua/unit/addr.lua index 6ec058c3f..03ceb2dbd 100644 --- a/test/lua/unit/addr.lua +++ b/test/lua/unit/addr.lua @@ -2,7 +2,7 @@ context("Inet addr check functions", function() local ffi = require("ffi") - + ffi.cdef[[ typedef struct rspamd_inet_addr_s rspamd_inet_addr_t; bool rspamd_parse_inet_address (rspamd_inet_addr_t **target, @@ -16,6 +16,8 @@ context("Inet addr check functions", function() {'256.1.1.1', false}, {'/tmp/socket', true}, {'./socket', true}, + {'[fe80::f919:8b26:ff93:3092%5]', true}, + {'[fe80::f919:8b26:ff93:3092]', true}, } for i,c in ipairs(cases) do diff --git a/test/lua/unit/base64.lua b/test/lua/unit/base64.lua index 1ed04f6f7..add031a45 100644 --- a/test/lua/unit/base64.lua +++ b/test/lua/unit/base64.lua @@ -114,9 +114,12 @@ vehemence of any carnal pleasure.]] assert_equal(cmp, 0, "fuzz test failed for length: " .. tostring(l)) end end) + + local speed_iters = 10000 + test("Base64 test reference vectors 1K", function() local t1 = ffi.C.rspamd_get_ticks() - local res = ffi.C.base64_test(true, 1000000, 1024) + local res = ffi.C.base64_test(true, speed_iters, 1024) local t2 = ffi.C.rspamd_get_ticks() print("Reference base64 (1K): " .. tostring(t2 - t1) .. " sec") @@ -124,7 +127,7 @@ vehemence of any carnal pleasure.]] end) test("Base64 test optimized vectors 1K", function() local t1 = ffi.C.rspamd_get_ticks() - local res = ffi.C.base64_test(false, 1000000, 1024) + local res = ffi.C.base64_test(false, speed_iters, 1024) local t2 = ffi.C.rspamd_get_ticks() print("Optimized base64 (1K): " .. tostring(t2 - t1) .. " sec") @@ -132,7 +135,7 @@ vehemence of any carnal pleasure.]] end) test("Base64 test reference vectors 512", function() local t1 = ffi.C.rspamd_get_ticks() - local res = ffi.C.base64_test(true, 1000000, 512) + local res = ffi.C.base64_test(true, speed_iters, 512) local t2 = ffi.C.rspamd_get_ticks() print("Reference base64 (512): " .. tostring(t2 - t1) .. " sec") @@ -140,7 +143,7 @@ vehemence of any carnal pleasure.]] end) test("Base64 test optimized vectors 512", function() local t1 = ffi.C.rspamd_get_ticks() - local res = ffi.C.base64_test(false, 1000000, 512) + local res = ffi.C.base64_test(false, speed_iters, 512) local t2 = ffi.C.rspamd_get_ticks() print("Optimized base64 (512): " .. tostring(t2 - t1) .. " sec") @@ -148,7 +151,7 @@ vehemence of any carnal pleasure.]] end) test("Base64 test reference vectors 10K", function() local t1 = ffi.C.rspamd_get_ticks() - local res = ffi.C.base64_test(true, 100000, 10240) + local res = ffi.C.base64_test(true, speed_iters / 100, 10240) local t2 = ffi.C.rspamd_get_ticks() print("Reference base64 (10K): " .. tostring(t2 - t1) .. " sec") @@ -156,7 +159,7 @@ vehemence of any carnal pleasure.]] end) test("Base64 test optimized vectors 10K", function() local t1 = ffi.C.rspamd_get_ticks() - local res = ffi.C.base64_test(false, 100000, 10240) + local res = ffi.C.base64_test(false, speed_iters / 100, 10240) local t2 = ffi.C.rspamd_get_ticks() print("Optimized base64 (10K): " .. tostring(t2 - t1) .. " sec") diff --git a/test/lua/unit/html.lua b/test/lua/unit/html.lua index 489947ffb..a9a490e5b 100644 --- a/test/lua/unit/html.lua +++ b/test/lua/unit/html.lua @@ -21,7 +21,7 @@ context("HTML processing", function() <b>stuff</p>? </body> </html> - ]], "Hello, world! test data\r\nstuff?"}, + ]], "Hello, world! test\r\ndata\r\nstuff\r\n?"}, {[[ <?xml version="1.0" encoding="iso-8859-1"?> <!DOCTYPE html @@ -100,7 +100,7 @@ context("HTML processing", function() </body> </html> - ]], 'content heada headb\r\ndata1 data2\r\n'}, + ]], 'content\r\nheada headb\r\ndata1 data2\r\n'}, {[[ <html lang="en"> <head> diff --git a/test/lua/unit/lua_util.extract_specific_urls.lua b/test/lua/unit/lua_util.extract_specific_urls.lua new file mode 100644 index 000000000..9c8e4e187 --- /dev/null +++ b/test/lua/unit/lua_util.extract_specific_urls.lua @@ -0,0 +1,226 @@ +context("Lua util - extract_specific_urls", function() + local util = require 'lua_util' + local mpool = require "rspamd_mempool" + local fun = require "fun" + local url = require "rspamd_url" + local logger = require "rspamd_logger" + local ffi = require "ffi" + local rspamd_util = require "rspamd_util" + local rspamd_task = require "rspamd_task" + + ffi.cdef[[ + void rspamd_url_init (const char *tld_file); + unsigned ottery_rand_range(unsigned top); + void rspamd_http_normalize_path_inplace(char *path, size_t len, size_t *nlen); + ]] + + local test_dir = string.gsub(debug.getinfo(1).source, "^@(.+/)[^/]+$", "%1") + + ffi.C.rspamd_url_init(string.format('%s/%s', test_dir, "test_tld.dat")) + + local task_object = { + urls = {}, + cache_set = function(self, ...) end, + cache_get = function(self, ...) end, + get_urls = function(self, need_emails) return self.urls end + } + + local url_list = { + "google.com", + "mail.com", + "bizz.com", + "bing.com", + "example.com", + "gov.co.net", + "tesco.co.net", + "domain1.co.net", + "domain2.co.net", + "domain3.co.net", + "domain4.co.net", + "abc.org", + "icq.org", + "meet.org", + "domain1.org", + "domain2.org", + "domain3.org", + "domain3.org", + "test.com", + } + + local cases = { + {expect = url_list, filter = nil, limit = 9999, need_emails = true, prefix = 'p'}, + {expect = {}, filter = (function() return false end), limit = 9999, need_emails = true, prefix = 'p'}, + {expect = {"domain4.co.net", "test.com"}, filter = nil, limit = 2, need_emails = true, prefix = 'p'}, + { + expect = {"gov.co.net", "tesco.co.net", "domain1.co.net", "domain2.co.net", "domain3.co.net", "domain4.co.net"}, + filter = (function(s) return s:get_host():sub(-4) == ".net" end), + limit = 9999, + need_emails = true, + prefix = 'p' + }, + { + input = {"a.google.com", "b.google.com", "c.google.com", "a.net", "bb.net", "a.bb.net", "b.bb.net"}, + expect = {"a.bb.net", "b.google.com", "a.net", "bb.net", "a.google.com"}, + filter = nil, + limit = 9999, + esld_limit = 2, + need_emails = true, + prefix = 'p' + }, + { + input = {"abc@a.google.com", "b.google.com", "c.google.com", "a.net", "bb.net", "a.bb.net", "b.bb.net"}, + expect = {"abc@a.google.com", "a.bb.net", "b.google.com", "a.net", "bb.net", "abc@a.google.com"}, + filter = nil, + limit = 9999, + esld_limit = 2, + need_emails = true, + prefix = 'p' + } + } + + local function prepare_actual_result(actual) + return fun.totable(fun.map( + function(u) return u:get_raw():gsub('^%w+://', '') end, + actual + )) + end + + local pool = mpool.create() + + for i,c in ipairs(cases) do + + local function prepare_url_list(c) + return fun.totable(fun.map( + function (u) return url.create(pool, u) end, + c.input or url_list + )) + end + + test("extract_specific_urls, backward compatibility case #" .. i, function() + task_object.urls = prepare_url_list(c) + if (c.esld_limit) then + -- not awailable in deprecated version + return + end + local actual = util.extract_specific_urls(task_object, c.limit, c.need_emails, c.filter, c.prefix) + + local actual_result = prepare_actual_result(actual) + + --[[ + local s = logger.slog("%1 =?= %2", c.expect, actual_result) + print(s) --]] + + assert_equal(true, util.table_cmp(c.expect, actual_result), "checking that we got the same tables") + + end) + + test("extract_specific_urls " .. i, function() + task_object.urls = prepare_url_list(c) + + local actual = util.extract_specific_urls({ + task = task_object, + limit = c.limit, + esld_limit = c.esld_limit, + need_emails = c.need_emails, + filter = c.filter, + prefix = c.prefix, + }) + + local actual_result = prepare_actual_result(actual) + + --[[ + local s = logger.slog("case[%1] %2 =?= %3", i, c.expect, actual_result) + print(s) --]] + + assert_equal(true, util.table_cmp(c.expect, actual_result), "checking that we got the same tables") + + end) + end + +--[[ ******************* kinda functional *************************************** ]] + local test_dir = string.gsub(debug.getinfo(1).source, "^@(.+/)[^/]+$", "%1") + local tld_file = string.format('%s/%s', test_dir, "test_tld.dat") + + local config = { + options = { + filters = {'spf', 'dkim', 'regexp'}, + url_tld = tld_file, + dns = { + nameserver = {'8.8.8.8'} + }, + }, + logging = { + type = 'console', + level = 'debug' + }, + metric = { + name = 'default', + actions = { + reject = 100500, + }, + unknown_weight = 1 + } + } + + test("extract_specific_urls - from email", function() + local cfg = rspamd_util.config_from_ucl(config, "INIT_URL,INIT_LIBS,INIT_SYMCACHE,INIT_VALIDATE,INIT_PRELOAD_MAPS") + assert_not_nil(cfg) + + local msg = [[ +From: <> +To: <nobody@example.com> +Subject: test +Content-Type: multipart/alternative; + boundary="_000_6be055295eab48a5af7ad4022f33e2d0_" + +--_000_6be055295eab48a5af7ad4022f33e2d0_ +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: base64 + +Hello world + + +--_000_6be055295eab48a5af7ad4022f33e2d0_ +Content-Type: text/html; charset="utf-8" + +<html><body> +<a href="http://example.net">http://example.net</a> +<a href="http://example1.net">http://example1.net</a> +<a href="http://example2.net">http://example2.net</a> +<a href="http://example3.net">http://example3.net</a> +<a href="http://example4.net">http://example4.net</a> +<a href="http://domain1.com">http://domain1.com</a> +<a href="http://domain2.com">http://domain2.com</a> +<a href="http://domain3.com">http://domain3.com</a> +<a href="http://domain4.com">http://domain4.com</a> +<a href="http://domain5.com">http://domain5.com</a> +<a href="http://domain.com">http://example.net/</a> +</html> +]] + local expect = {"example.net", "domain.com"} + local res,task = rspamd_task.load_from_string(msg, rspamd_config) + + if not res then + assert_true(false, "failed to load message") + end + + if not task:process_message() then + assert_true(false, "failed to process message") + end + + local actual = util.extract_specific_urls({ + task = task, + limit = 2, + esld_limit = 2, + }) + + local actual_result = prepare_actual_result(actual) + + --[[ + local s = logger.slog("case[%1] %2 =?= %3", i, expect, actual_result) + print(s) --]] + + assert_equal("domain.com", actual_result[1], "checking that first url is the one with highest suspiciousness level") + + end) +end)
\ No newline at end of file diff --git a/test/lua/unit/received.lua b/test/lua/unit/received.lua new file mode 100644 index 000000000..6c133279f --- /dev/null +++ b/test/lua/unit/received.lua @@ -0,0 +1,137 @@ +-- inet addr tests + +context("Received headers parser", function() + local ffi = require("ffi") + + ffi.cdef[[ + struct received_header { + char *from_hostname; + char *from_ip; + char *real_hostname; + char *real_ip; + char *by_hostname; + char *for_mbox; + void *addr; + void *hdr; + long timestamp; + int type; + int flags; + }; + struct rspamd_task * rspamd_task_new(struct rspamd_worker *worker, struct rspamd_config *cfg); + int rspamd_smtp_received_parse (struct rspamd_task *task, + const char *data, size_t len, struct received_header *rh); + ]] + + local cases = { + {[[from out-9.smtp.github.com (out-9.smtp.github.com [192.30.254.192]) + (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) + (No client certificate requested) + by mail.highsecure.ru (Postfix) with ESMTPS id C7B1A30014A + for <xxx@xxx.xxx>; Tue, 3 Jul 2018 14:40:19 +0200 (CEST)]], + { + from_hostname = 'out-9.smtp.github.com', + from_ip = '192.30.254.192', + real_ip = '192.30.254.192', + by_hostname = 'mail.highsecure.ru', + } + }, + {[[from localhost ([127.0.0.1]:49019 helo=hummus.csx.cam.ac.uk) + by hummus.csx.cam.ac.uk with esmtp (Exim 4.91-pdpfix1) + (envelope-from <exim-dev-bounces@exim.org>) + id 1fZ55o-0006DP-3H + for <xxx@xxx.xxx>; Sat, 30 Jun 2018 02:54:28 +0100]], + { + from_hostname = 'localhost', + from_ip = '127.0.0.1', + real_ip = '127.0.0.1', + by_hostname = 'hummus.csx.cam.ac.uk', + } + }, + {[[from smtp.spodhuis.org ([2a02:898:31:0:48:4558:736d:7470]:38689 + helo=mx.spodhuis.org) + by hummus.csx.cam.ac.uk with esmtpsa (TLSv1.3:TLS_AES_256_GCM_SHA384:256) + (Exim 4.91-pdpfix1+cc) (envelope-from <xxx@exim.org>) + id 1fZ55k-0006CO-9M + for exim-dev@exim.org; Sat, 30 Jun 2018 02:54:24 +0100]], + { + from_hostname = 'smtp.spodhuis.org', + from_ip = '2a02:898:31:0:48:4558:736d:7470', + real_ip = '2a02:898:31:0:48:4558:736d:7470', + by_hostname = 'hummus.csx.cam.ac.uk', + } + }, + {'from aaa.cn ([1.1.1.1]) by localhost.localdomain (Haraka/2.8.18) with ESMTPA id 349C9C2B-491A-4925-A687-3EF14038C344.1 envelope-from <huxin@xxx.com> (authenticated bits=0); Tue, 03 Jul 2018 14:18:13 +0200', + { + from_hostname = 'aaa.cn', + from_ip = '1.1.1.1', + real_ip = '1.1.1.1', + } + }, + {'from [192.83.172.101] by (HELLO 148.251.238.35 ) (148.251.238.35) by guovswzqkvry051@sohu.com with gg login by AOL 6.0 for Windows US sub 008 SMTP ; Tue, 03 Jul 2018 09:01:47 -0300', + { + from_ip = '192.83.172.101', + by_hostname = '', + } + }, + } + + local task = ffi.C.rspamd_task_new(nil, nil) + local NULL = ffi.new 'void*' + local function ffi_string(fs) + if fs ~= NULL then return ffi.string(fs) end + return nil + end + + for i,c in ipairs(cases) do + test("Parse received " .. i, function() + local hdr = ffi.new("struct received_header") + c[1] = c[1]:gsub('\n', ' ') -- Replace folding + ffi.C.rspamd_smtp_received_parse(task, c[1], #c[1], hdr) + + for k,v in pairs(c[2]) do + if k == 'from_hostname' then + if #v > 0 then + assert_equal(v, ffi_string(hdr.from_hostname), + string.format('%s: from_hostname: %s, expected: %s', + c[1], ffi_string(hdr.from_hostname), v)) + else + assert_nil(hdr.from_hostname, + string.format('%s: from_hostname: %s, expected: nil', + c[1], ffi_string(hdr.from_hostname))) + end + elseif k == 'from_ip' then + if #v > 0 then + assert_equal(v, ffi_string(hdr.from_ip), + string.format('%s: from_ip: %s, expected: %s', + c[1], ffi_string(hdr.from_ip), v)) + else + assert_nil(hdr.from_ip, + string.format('%s: from_ip: %s, expected: nil', + c[1], ffi_string(hdr.from_ip))) + end + elseif k == 'real_ip' then + if #v > 0 then + assert_equal(v, ffi_string(hdr.real_ip), + string.format('%s: real_ip: %s, expected: %s', + c[1], ffi_string(hdr.real_ip), v)) + else + assert_nil(hdr.real_ip, + string.format('%s: real_ip: %s, expected: nil', + c[1], ffi_string(hdr.real_ip))) + end + elseif k == 'by_hostname' then + if #v > 0 then + assert_equal(v, ffi_string(hdr.by_hostname), + string.format('%s: by_hostname: %s, expected: %s', + c[1], ffi_string(hdr.by_hostname), v)) + else + assert_nil(hdr.by_hostname, + string.format('%s: by_hostname: %s, expected: nil', + c[1], ffi_string(hdr.by_hostname))) + end + end + end + end) + + end +end)
\ No newline at end of file diff --git a/test/lua/unit/siphash.lua b/test/lua/unit/siphash.lua index 3c92d2581..13f02eaf3 100644 --- a/test/lua/unit/siphash.lua +++ b/test/lua/unit/siphash.lua @@ -11,9 +11,11 @@ context("Siphash check functions", function() ffi.C.rspamd_cryptobox_init() + local speed_iters = 1000 + test("Siphash test reference vectors (1KB)", function() local t1 = ffi.C.rspamd_get_ticks() - local res = ffi.C.siphash24_test(true, 100000, 1024) + local res = ffi.C.siphash24_test(true, speed_iters, 1024) local t2 = ffi.C.rspamd_get_ticks() print("Reference siphash (1KB): " .. tostring(t2 - t1) .. " sec") @@ -21,7 +23,7 @@ context("Siphash check functions", function() end) test("Siphash test optimized vectors (1KB)", function() local t1 = ffi.C.rspamd_get_ticks() - local res = ffi.C.siphash24_test(false, 100000, 1024) + local res = ffi.C.siphash24_test(false, speed_iters, 1024) local t2 = ffi.C.rspamd_get_ticks() print("Optimized siphash (1KB): " .. tostring(t2 - t1) .. " sec") @@ -29,7 +31,7 @@ context("Siphash check functions", function() end) test("Siphash test reference vectors (5B)", function() local t1 = ffi.C.rspamd_get_ticks() - local res = ffi.C.siphash24_test(true, 1000000, 5) + local res = ffi.C.siphash24_test(true, speed_iters, 5) local t2 = ffi.C.rspamd_get_ticks() print("Reference siphash (5B): " .. tostring(t2 - t1) .. " sec") @@ -37,7 +39,7 @@ context("Siphash check functions", function() end) test("Siphash test optimized vectors (5B)", function() local t1 = ffi.C.rspamd_get_ticks() - local res = ffi.C.siphash24_test(false, 1000000, 5) + local res = ffi.C.siphash24_test(false, speed_iters, 5) local t2 = ffi.C.rspamd_get_ticks() print("Optimized siphash (5B): " .. tostring(t2 - t1) .. " sec") @@ -45,7 +47,7 @@ context("Siphash check functions", function() end) test("Siphash test reference vectors (50B)", function() local t1 = ffi.C.rspamd_get_ticks() - local res = ffi.C.siphash24_test(true, 1000000, 50) + local res = ffi.C.siphash24_test(true, speed_iters, 50) local t2 = ffi.C.rspamd_get_ticks() print("Reference siphash (50B): " .. tostring(t2 - t1) .. " sec") @@ -53,7 +55,7 @@ context("Siphash check functions", function() end) test("Siphash test optimized vectors (50B)", function() local t1 = ffi.C.rspamd_get_ticks() - local res = ffi.C.siphash24_test(false, 1000000, 50) + local res = ffi.C.siphash24_test(false, speed_iters, 50) local t2 = ffi.C.rspamd_get_ticks() print("Optimized siphash (50B): " .. tostring(t2 - t1) .. " sec") diff --git a/test/lua/unit/url.lua b/test/lua/unit/url.lua index ccde222f8..902744c2e 100644 --- a/test/lua/unit/url.lua +++ b/test/lua/unit/url.lua @@ -95,6 +95,9 @@ context("URL check functions", function() {"http://twitter.com#test", true, { host = 'twitter.com', fragment = 'test' }}, + {"http:www.twitter.com#test", true, { + host = 'www.twitter.com', fragment = 'test' + }}, } -- Some cases from https://code.google.com/p/google-url/source/browse/trunk/src/url_canon_unittest.cc diff --git a/test/rspamd_upstream_test.c b/test/rspamd_upstream_test.c index cce0008a9..4e4f1ae87 100644 --- a/test/rspamd_upstream_test.c +++ b/test/rspamd_upstream_test.c @@ -103,7 +103,7 @@ rspamd_upstream_test_func (void) next_addr = rspamd_upstream_addr (up); g_assert (rspamd_inet_address_get_af (next_addr) == AF_INET6); /* Test errors with IPv6 */ - rspamd_upstream_fail (up); + rspamd_upstream_fail (up, TRUE); /* Now we should have merely IPv4 addresses in rotation */ addr = rspamd_upstream_addr (up); for (i = 0; i < 256; i++) { @@ -166,7 +166,7 @@ rspamd_upstream_test_func (void) up = rspamd_upstream_get (ls, RSPAMD_UPSTREAM_MASTER_SLAVE, NULL, 0); for (i = 0; i < 100; i ++) { - rspamd_upstream_fail (up); + rspamd_upstream_fail (up, TRUE); } g_assert (rspamd_upstreams_alive (ls) == 2); diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 0f65b06ce..036365439 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -16,9 +16,7 @@ MACRO(ADD_UTIL NAME) IF (ENABLE_SNOWBALL MATCHES "ON") TARGET_LINK_LIBRARIES("${NAME}" stemmer) ENDIF() - IF(ENABLE_HIREDIS MATCHES "ON") - TARGET_LINK_LIBRARIES("${NAME}" rspamd-hiredis) - ENDIF() + TARGET_LINK_LIBRARIES("${NAME}" rspamd-hiredis) TARGET_LINK_LIBRARIES(${NAME} rspamd-linenoise) TARGET_LINK_LIBRARIES("${NAME}" ${RSPAMD_REQUIRED_LIBRARIES}) ENDMACRO() diff --git a/utils/rspamd_stats.pl b/utils/rspamd_stats.pl index b997ff525..ac7b1349f 100755 --- a/utils/rspamd_stats.pl +++ b/utils/rspamd_stats.pl @@ -508,12 +508,22 @@ sub ProcessLog { next if ( $skip != 0 ); - $timeStamp{'end'} = $ts; - $timeStamp{'start'} //= $timeStamp{'end'}; - $scanTime{'min'} = $scan_time - if ( !exists $scanTime{'min'} || $scanTime{'min'} > $scan_time ); - $scanTime{'max'} = $scan_time - if ( $scanTime{'max'} < $scan_time ); + if (defined($timeStamp{'end'})) { + $timeStamp{'end'} = $ts if ( $ts gt $timeStamp{'end'} ); + } + else { + $timeStamp{'end'} = $ts; + } + + if (defined($timeStamp{'start'})) { + $timeStamp{'start'} = $ts if ( $ts lt $timeStamp{'start'} ); + } + else { + $timeStamp{'start'} = $ts; + } + + $scanTime{'min'} = $scan_time if ( !exists $scanTime{'min'} || $scanTime{'min'} > $scan_time ); + $scanTime{'max'} = $scan_time if ( $scanTime{'max'} < $scan_time ); $scanTime{'total'} += $scan_time; $action{$act}++; |