aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorheraklit256 <37872459+heraklit256@users.noreply.github.com>2018-08-07 14:17:55 +0000
committerGitHub <noreply@github.com>2018-08-07 14:17:55 +0000
commitd0c411efd03829a3caa71fca55170136531f0fb2 (patch)
treef8208bdc181e952105e3ed7344b8e8256f0cce60
parent728b3a44311cab0717fc1ef9d1576ed94f7118e4 (diff)
parent043e80725dbacdae4c9589e2558f4b33faf5776a (diff)
downloadrspamd-d0c411efd03829a3caa71fca55170136531f0fb2.tar.gz
rspamd-d0c411efd03829a3caa71fca55170136531f0fb2.zip
Merge pull request #3 from vstakhov/master
merge upstream into local master
-rw-r--r--.circleci/config.yml150
-rw-r--r--.eslintrc.json77
-rw-r--r--.luacheckrc1
-rw-r--r--CMakeLists.txt28
-rw-r--r--ChangeLog8385
-rw-r--r--centos/rspamd.spec10
-rw-r--r--centos/sources/rspamd.init19
-rw-r--r--circle.yml22
-rw-r--r--conf/dmarc_whitelist.inc5
-rw-r--r--conf/modules.d/clickhouse.conf12
-rw-r--r--conf/modules.d/fuzzy_check.conf2
-rw-r--r--conf/modules.d/mid.conf5
-rw-r--r--conf/modules.d/mime_types.conf5
-rw-r--r--conf/modules.d/phishing.conf7
-rw-r--r--conf/modules.d/rbl.conf10
-rw-r--r--conf/modules.d/reputation.conf35
-rw-r--r--conf/modules.d/surbl.conf12
-rw-r--r--conf/modules.d/whitelist.conf12
-rw-r--r--conf/redirectors.inc2
-rw-r--r--conf/scores.d/policies_group.conf4
-rw-r--r--conf/scores.d/rbl_group.conf5
-rw-r--r--conf/spf_dkim_whitelist.inc2
-rw-r--r--config.h.in3
-rw-r--r--contrib/librdns/dns_private.h8
-rw-r--r--contrib/librdns/rdns.h7
-rw-r--r--contrib/librdns/resolver.c117
-rw-r--r--contrib/librdns/util.c15
-rw-r--r--contrib/libucl/khash.h7
-rw-r--r--contrib/libucl/ucl_util.c4
-rw-r--r--contrib/lua-torch/torch7/lib/TH/THGeneral.c16
-rw-r--r--contrib/t1ha/CMakeLists.txt2
-rw-r--r--contrib/t1ha/t1ha_bits.h34
-rw-r--r--debian/control2
-rwxr-xr-xdebian/rules2
-rw-r--r--doc/rspamc.143
-rw-r--r--doc/rspamc.1.md2
-rw-r--r--interface/index.html2
-rw-r--r--interface/js/app/config.js472
-rw-r--r--interface/js/app/graph.js447
-rw-r--r--interface/js/app/history.js1075
-rw-r--r--interface/js/app/rspamd.js938
-rw-r--r--interface/js/app/stats.js383
-rw-r--r--interface/js/app/symbols.js460
-rw-r--r--interface/js/app/upload.js338
-rw-r--r--interface/js/lib/require.min.js5
-rw-r--r--interface/js/main.js56
-rw-r--r--interface/js/require.js5
-rw-r--r--lualib/lua_auth_results.lua3
-rw-r--r--lualib/lua_clickhouse.lua314
-rw-r--r--lualib/lua_meta.lua18
-rw-r--r--lualib/lua_redis.lua4
-rw-r--r--lualib/lua_squeeze_rules.lua5
-rw-r--r--lualib/lua_stat.lua32
-rw-r--r--lualib/lua_util.lua142
-rw-r--r--lualib/rspamadm/confighelp.lua2
-rw-r--r--lualib/rspamadm/configwizard.lua21
-rw-r--r--lualib/rspamadm/keypair.lua220
-rw-r--r--lualib/rspamadm/mime.lua543
-rw-r--r--package.json6
-rw-r--r--rules/headers_checks.lua66
-rw-r--r--rules/html.lua122
-rw-r--r--rules/regexp/headers.lua9
-rw-r--r--rules/rspamd.lua17
-rw-r--r--src/CMakeLists.txt4
-rw-r--r--src/client/rspamc.c46
-rw-r--r--src/controller.c62
-rw-r--r--src/fuzzy_storage.c189
-rw-r--r--src/libcryptobox/keypair.c62
-rw-r--r--src/libcryptobox/keypair.h20
-rw-r--r--src/libcryptobox/keypair_private.h1
-rw-r--r--src/libcryptobox/keypairs_cache.c1
-rw-r--r--src/libmime/email_addr.c2
-rw-r--r--src/libmime/filter.c218
-rw-r--r--src/libmime/filter.h53
-rw-r--r--src/libmime/lang_detection.c48
-rw-r--r--src/libmime/lang_detection.h4
-rw-r--r--src/libmime/message.c29
-rw-r--r--src/libmime/mime_encoding.c2
-rw-r--r--src/libmime/mime_expressions.c40
-rw-r--r--src/libmime/mime_parser.c34
-rw-r--r--src/libserver/CMakeLists.txt6
-rw-r--r--src/libserver/cfg_file.h14
-rw-r--r--src/libserver/cfg_rcl.c19
-rw-r--r--src/libserver/cfg_utils.c101
-rw-r--r--src/libserver/composites.c49
-rw-r--r--src/libserver/dkim.c86
-rw-r--r--src/libserver/dkim.h51
-rw-r--r--src/libserver/dns.c248
-rw-r--r--src/libserver/dynamic_cfg.c45
-rw-r--r--src/libserver/events.c165
-rw-r--r--src/libserver/fuzzy_backend.c123
-rw-r--r--src/libserver/fuzzy_backend.h7
-rw-r--r--src/libserver/fuzzy_backend_redis.c160
-rw-r--r--src/libserver/fuzzy_wire.h5
-rw-r--r--src/libserver/html.c634
-rw-r--r--src/libserver/html.h8
-rw-r--r--src/libserver/mempool_vars_internal.h1
-rw-r--r--src/libserver/milter.c8
-rw-r--r--src/libserver/monitored.c34
-rw-r--r--src/libserver/protocol.c27
-rw-r--r--src/libserver/roll_history.c16
-rw-r--r--src/libserver/rspamd_control.c3
-rw-r--r--src/libserver/spf.c87
-rw-r--r--src/libserver/symbols_cache.c231
-rw-r--r--src/libserver/symbols_cache.h1
-rw-r--r--src/libserver/task.c29
-rw-r--r--src/libserver/url.c68
-rw-r--r--src/libserver/url.h2
-rw-r--r--src/libserver/worker_util.c159
-rw-r--r--src/libserver/worker_util.h5
-rw-r--r--src/libstat/CMakeLists.txt10
-rw-r--r--src/libstat/backends/redis_backend.c26
-rw-r--r--src/libstat/learn_cache/redis_cache.c6
-rw-r--r--src/libstat/stat_api.h19
-rw-r--r--src/libstat/stat_config.c28
-rw-r--r--src/libstat/stat_internal.h9
-rw-r--r--src/libstat/stat_process.c19
-rw-r--r--src/libutil/addr.c14
-rw-r--r--src/libutil/fstring.c22
-rw-r--r--src/libutil/http.c50
-rw-r--r--src/libutil/http.h2
-rw-r--r--src/libutil/logger.c2
-rw-r--r--src/libutil/map.c851
-rw-r--r--src/libutil/map.h36
-rw-r--r--src/libutil/map_helpers.c31
-rw-r--r--src/libutil/map_helpers.h4
-rw-r--r--src/libutil/map_private.h52
-rw-r--r--src/libutil/mem_pool.c40
-rw-r--r--src/libutil/printf.c343
-rw-r--r--src/libutil/printf.h1
-rw-r--r--src/libutil/sqlite_utils.c4
-rw-r--r--src/libutil/str_util.c47
-rw-r--r--src/libutil/str_util.h8
-rw-r--r--src/libutil/upstream.c12
-rw-r--r--src/libutil/upstream.h2
-rw-r--r--src/libutil/util.c118
-rw-r--r--src/libutil/util.h28
-rw-r--r--src/lua/lua_common.h16
-rw-r--r--src/lua/lua_config.c78
-rw-r--r--src/lua/lua_cryptobox.c112
-rw-r--r--src/lua/lua_html.c23
-rw-r--r--src/lua/lua_http.c54
-rw-r--r--src/lua/lua_logger.c14
-rw-r--r--src/lua/lua_map.c34
-rw-r--r--src/lua/lua_mimepart.c168
-rw-r--r--src/lua/lua_task.c550
-rw-r--r--src/lua/lua_upstream.c8
-rw-r--r--src/lua/lua_util.c73
-rw-r--r--src/plugins/chartable.c74
-rw-r--r--src/plugins/dkim_check.c234
-rw-r--r--src/plugins/fuzzy_check.c182
-rw-r--r--src/plugins/lua/antivirus.lua121
-rw-r--r--src/plugins/lua/arc.lua55
-rw-r--r--src/plugins/lua/asn.lua3
-rw-r--r--src/plugins/lua/bayes_expiry.lua2
-rw-r--r--src/plugins/lua/clickhouse.lua563
-rw-r--r--src/plugins/lua/dkim_signing.lua57
-rw-r--r--src/plugins/lua/dmarc.lua6
-rw-r--r--src/plugins/lua/dynamic_conf.lua19
-rw-r--r--src/plugins/lua/elastic.lua73
-rw-r--r--src/plugins/lua/greylist.lua3
-rw-r--r--src/plugins/lua/maillist.lua2
-rw-r--r--src/plugins/lua/metric_exporter.lua5
-rw-r--r--src/plugins/lua/multimap.lua33
-rw-r--r--src/plugins/lua/phishing.lua191
-rw-r--r--src/plugins/lua/ratelimit.lua244
-rw-r--r--src/plugins/lua/reputation.lua180
-rw-r--r--src/plugins/lua/settings.lua18
-rw-r--r--src/plugins/lua/spamassassin.lua10
-rw-r--r--src/plugins/lua/url_redirector.lua2
-rw-r--r--src/plugins/lua/whitelist.lua3
-rw-r--r--src/plugins/regexp.c53
-rw-r--r--src/plugins/spf.c53
-rw-r--r--src/plugins/surbl.c220
-rw-r--r--src/ragel/content_disposition.rl1
-rw-r--r--src/ragel/content_disposition_parser.rl2
-rw-r--r--src/ragel/smtp_addr_parser.rl4
-rw-r--r--src/ragel/smtp_address.rl4
-rw-r--r--src/ragel/smtp_date.rl2
-rw-r--r--src/ragel/smtp_date_parser.rl1
-rw-r--r--src/ragel/smtp_received.rl7
-rw-r--r--src/ragel/smtp_received_parser.rl4
-rw-r--r--src/rspamadm/configdump.c3
-rw-r--r--src/rspamadm/confighelp.c14
-rw-r--r--src/rspamadm/control.c3
-rw-r--r--src/rspamadm/fuzzy_convert.c3
-rw-r--r--src/rspamadm/rspamadm.c94
-rw-r--r--src/rspamadm/rspamadm.h3
-rw-r--r--src/rspamadm/stat_convert.c7
-rw-r--r--src/rspamd.c50
-rw-r--r--src/rspamd.h3
-rw-r--r--src/rspamd_proxy.c16
-rw-r--r--src/worker.c2
-rw-r--r--test/CMakeLists.txt4
-rw-r--r--test/functional/cases/100_general.robot18
-rw-r--r--test/functional/cases/102_multimap.robot8
-rw-r--r--test/functional/cases/105_mimetypes.robot4
-rw-r--r--test/functional/cases/120_fuzzy/replication.robot84
-rw-r--r--test/functional/cases/130_dkim.robot8
-rw-r--r--test/functional/cases/131_dkim_signing/001_simple.robot6
-rw-r--r--test/functional/cases/140_proxy.robot4
-rw-r--r--test/functional/configs/dkim.conf64
-rw-r--r--test/functional/configs/dkim_signing/simple.conf2
-rw-r--r--test/functional/configs/maps/top.list2
-rw-r--r--test/functional/configs/mime_types.conf5
-rw-r--r--test/functional/configs/multimap.conf5
-rw-r--r--test/functional/configs/nginx.conf20
-rw-r--r--test/functional/configs/plugins.conf57
-rw-r--r--test/functional/configs/trivial.conf14
-rw-r--r--test/functional/lib/rspamd.robot20
-rw-r--r--test/functional/lib/vars.py2
-rw-r--r--test/functional/messages/dmarc/fail_none1.eml3
-rw-r--r--test/functional/messages/ed25519-broken.eml26
-rw-r--r--test/functional/messages/ed25519.eml26
-rw-r--r--test/functional/messages/empty-plain-text.eml14
-rw-r--r--test/functional/messages/gtube.eml33
-rw-r--r--test/functional/messages/zerofont.eml17
-rw-r--r--test/lua/unit/addr.lua4
-rw-r--r--test/lua/unit/base64.lua15
-rw-r--r--test/lua/unit/html.lua4
-rw-r--r--test/lua/unit/lua_util.extract_specific_urls.lua226
-rw-r--r--test/lua/unit/received.lua137
-rw-r--r--test/lua/unit/siphash.lua14
-rw-r--r--test/lua/unit/url.lua3
-rw-r--r--test/rspamd_upstream_test.c4
-rw-r--r--utils/CMakeLists.txt4
-rwxr-xr-xutils/rspamd_stats.pl22
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()
diff --git a/ChangeLog b/ChangeLog
index dd6787e7a..ff9606872 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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>&nbsp;<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>&nbsp;<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 = {
- '&': '&amp;',
- '<': '&lt;',
- '>': '&gt;',
- '"': '&quot;',
- "'": '&#39;',
- '/': '&#x2F;',
- '`': '&#x60;',
- '=': '&#x3D;'
- };
- 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 = {
+ "&": "&amp;",
+ "<": "&lt;",
+ ">": "&gt;",
+ "\"": "&quot;",
+ "'": "&#39;",
+ "/": "&#x2F;",
+ "`": "&#x60;",
+ "=": "&#x3D;"
+ };
+ 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(",&#8203;") + more("rcpt_smtp") + "]";
+ if (mime) {
+ full += " ";
+ shrt += " ";
+ }
+ }
+ if (mime) {
+ full += item.rcpt_mime.join(", ");
+ shrt += item.rcpt_mime.slice(0, rcpt_lim).join(",&#8203;") + 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(",&#8203;") + "] " + item.rcpt_mime.join(",&#8203;");
- } else {
- item.rcpt_mime = item.rcpt_mime.join(",&#8203;");
- }
- 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">&times;</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\">&times;</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>' +
- '&nbsp;<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>&nbsp;" +
+ "<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}++;