aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt532
-rw-r--r--src/controller.c116
-rw-r--r--src/libserver/fuzzy_backend/fuzzy_backend_redis.c4
-rw-r--r--src/libserver/maps/map.c102
-rw-r--r--src/libserver/maps/map_private.h36
-rw-r--r--src/libserver/symcache/symcache_impl.cxx4
-rw-r--r--src/lua/lua_common.h3
-rw-r--r--src/lua/lua_config.c4
-rw-r--r--src/lua/lua_logger.c485
-rw-r--r--src/lua/lua_map.c7
-rw-r--r--src/plugins/lua/contextal.lua332
-rw-r--r--src/plugins/lua/greylist.lua23
-rw-r--r--src/plugins/lua/hfilter.lua13
-rw-r--r--src/rspamadm/lua_repl.c12
14 files changed, 1070 insertions, 603 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 92edb0b6a..6cc49e4e4 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,246 +1,286 @@
-MACRO(_AddModulesForced MLIST)
-# Generate unique string for this build
- SET(MODULES_C "${CMAKE_CURRENT_BINARY_DIR}/modules.c")
- FILE(WRITE "${MODULES_C}"
- "#include \"rspamd.h\"\n")
-
- # Handle even old cmake
- LIST(LENGTH ${MLIST} MLIST_COUNT)
- MATH(EXPR MLIST_MAX ${MLIST_COUNT}-1)
-
- FOREACH(MOD_IDX RANGE ${MLIST_MAX})
- LIST(GET ${MLIST} ${MOD_IDX} MOD)
- FILE(APPEND "${MODULES_C}" "extern module_t ${MOD}_module;\n")
- ENDFOREACH(MOD_IDX RANGE ${MLIST_MAX})
-
- FILE(APPEND "${MODULES_C}" "\n\nmodule_t *modules[] = {\n")
-
- FOREACH(MOD_IDX RANGE ${MLIST_MAX})
- LIST(GET ${MLIST} ${MOD_IDX} MOD)
- FILE(APPEND "${MODULES_C}" "&${MOD}_module,\n")
- ENDFOREACH(MOD_IDX RANGE ${MLIST_MAX})
-
- FILE(APPEND "${MODULES_C}" "NULL\n};\n")
-ENDMACRO(_AddModulesForced MLIST)
-
-MACRO(_AddWorkersForced WLIST)
- SET(WORKERS_C "${CMAKE_CURRENT_BINARY_DIR}/workers.c")
- FILE(WRITE "${WORKERS_C}"
- "#include \"rspamd.h\"\n")
-
- # Handle even old cmake
- LIST(LENGTH ${WLIST} WLIST_COUNT)
- MATH(EXPR WLIST_MAX ${WLIST_COUNT}-1)
- FOREACH(MOD_IDX RANGE ${WLIST_MAX})
- LIST(GET ${WLIST} ${MOD_IDX} WRK)
- FILE(APPEND "${WORKERS_C}" "extern worker_t ${WRK}_worker;\n")
- ENDFOREACH(MOD_IDX RANGE ${WLIST_MAX})
-
- FILE(APPEND "${WORKERS_C}" "\n\nworker_t *workers[] = {\n")
-
- FOREACH(MOD_IDX RANGE ${WLIST_MAX})
- LIST(GET ${WLIST} ${MOD_IDX} WRK)
- FILE(APPEND "${WORKERS_C}" "&${WRK}_worker,\n")
- ENDFOREACH(MOD_IDX RANGE ${WLIST_MAX})
- FILE(APPEND "${WORKERS_C}" "NULL\n};\n")
-ENDMACRO(_AddWorkersForced WLIST)
-
-MACRO(AddModules MLIST WLIST)
- _AddModulesForced(${MLIST})
- _AddWorkersForced(${WLIST})
- #IF(NOT EXISTS "modules.c")
- # _AddModulesForced(${MLIST} ${WLIST})
- #ELSE(NOT EXISTS "modules.c")
- # FILE(STRINGS "modules.c" FILE_ID_RAW REGEX "^/.*[a-zA-Z0-9]+.*/$")
- # STRING(REGEX MATCH "[a-zA-Z0-9]+" FILE_ID "${FILE_ID_RAW}")
- # IF(NOT FILE_ID STREQUAL MODULES_ID)
- # MESSAGE("Regenerate modules info")
- # _AddModulesForced(${MLIST} ${WLIST})
- # ENDIF(NOT FILE_ID STREQUAL MODULES_ID)
- #ENDIF(NOT EXISTS "modules.c")
-ENDMACRO(AddModules MLIST WLIST)
-
-# Rspamd core components
-IF (ENABLE_CLANG_PLUGIN MATCHES "ON")
- SET(CMAKE_C_FLAGS
- "${CMAKE_C_FLAGS} -Xclang -load -Xclang ${CMAKE_CURRENT_BINARY_DIR}/../clang-plugin/librspamd-clang${CMAKE_SHARED_LIBRARY_SUFFIX} -Xclang -add-plugin -Xclang rspamd-ast")
- IF(CLANG_EXTRA_PLUGINS_LIBS)
- FOREACH(_lib ${CLANG_EXTRA_PLUGINS_LIBS})
- SET(CMAKE_C_FLAGS
- "${CMAKE_C_FLAGS} -Xclang -load -Xclang ${_lib}")
- SET(CMAKE_CXX_FLAGS
- "${CMAKE_CXX_FLAGS} -Xclang -load -Xclang ${_lib}")
- ENDFOREACH()
- ENDIF()
- IF(CLANG_EXTRA_PLUGINS)
- FOREACH(_plug ${CLANG_EXTRA_PLUGINS})
- SET(CMAKE_C_FLAGS
- "${CMAKE_C_FLAGS} -Xclang -add-plugin -Xclang ${_plug}")
- SET(CMAKE_CXX_FLAGS
- "${CMAKE_C_FLAGS} -Xclang -add-plugin -Xclang ${_plug}")
- ENDFOREACH()
- ENDIF()
-ENDIF ()
-
-ADD_SUBDIRECTORY(lua)
-ADD_SUBDIRECTORY(libcryptobox)
-ADD_SUBDIRECTORY(libutil)
-ADD_SUBDIRECTORY(libserver)
-ADD_SUBDIRECTORY(libmime)
-ADD_SUBDIRECTORY(libstat)
-ADD_SUBDIRECTORY(client)
-ADD_SUBDIRECTORY(rspamadm)
-
-SET(RSPAMDSRC controller.c
- fuzzy_storage.c
- rspamd.c
- worker.c
- rspamd_proxy.c)
-
-SET(PLUGINSSRC plugins/regexp.c
- plugins/chartable.cxx
- plugins/fuzzy_check.c
- plugins/dkim_check.c
- libserver/rspamd_control.c)
-
-SET(MODULES_LIST regexp chartable fuzzy_check dkim)
-SET(WORKERS_LIST normal controller fuzzy rspamd_proxy)
-IF (ENABLE_HYPERSCAN MATCHES "ON")
- LIST(APPEND WORKERS_LIST "hs_helper")
- LIST(APPEND RSPAMDSRC "hs_helper.c")
-ENDIF()
-
-AddModules(MODULES_LIST WORKERS_LIST)
-LIST(LENGTH PLUGINSSRC RSPAMD_MODULES_NUM)
-
-SET(RAGEL_DEPENDS "${CMAKE_SOURCE_DIR}/src/ragel/smtp_address.rl"
- "${CMAKE_SOURCE_DIR}/src/ragel/smtp_date.rl"
- "${CMAKE_SOURCE_DIR}/src/ragel/smtp_ip.rl"
- "${CMAKE_SOURCE_DIR}/src/ragel/smtp_base.rl"
- "${CMAKE_SOURCE_DIR}/src/ragel/content_disposition.rl")
-RAGEL_TARGET(ragel_smtp_addr
- INPUTS ${CMAKE_SOURCE_DIR}/src/ragel/smtp_addr_parser.rl
- DEPENDS ${RAGEL_DEPENDS}
- COMPILE_FLAGS -T1
- OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/smtp_addr_parser.rl.c)
-RAGEL_TARGET(ragel_content_disposition
- INPUTS ${CMAKE_SOURCE_DIR}/src/ragel/content_disposition_parser.rl
- DEPENDS ${RAGEL_DEPENDS}
- COMPILE_FLAGS -G2
- OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/content_disposition.rl.c)
-RAGEL_TARGET(ragel_rfc2047
- INPUTS ${CMAKE_SOURCE_DIR}/src/ragel/rfc2047_parser.rl
- DEPENDS ${RAGEL_DEPENDS}
- COMPILE_FLAGS -G2
- OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/rfc2047.rl.c)
-RAGEL_TARGET(ragel_smtp_date
- INPUTS ${CMAKE_SOURCE_DIR}/src/ragel/smtp_date_parser.rl
- DEPENDS ${RAGEL_DEPENDS}
- COMPILE_FLAGS -G2
- OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/date_parser.rl.c)
-RAGEL_TARGET(ragel_smtp_ip
- INPUTS ${CMAKE_SOURCE_DIR}/src/ragel/smtp_ip_parser.rl
- DEPENDS ${RAGEL_DEPENDS}
- COMPILE_FLAGS -G2
- OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/ip_parser.rl.c)
-# Fucking cmake...
-FOREACH(_GEN ${LIBSERVER_GENERATED})
- set_source_files_properties(${_GEN} PROPERTIES GENERATED TRUE)
-ENDFOREACH()
-######################### LINK SECTION ###############################
-
-IF(ENABLE_STATIC MATCHES "ON")
- ADD_LIBRARY(rspamd-server STATIC
- ${RSPAMD_CRYPTOBOX}
- ${RSPAMD_UTIL}
- ${RSPAMD_LUA}
- ${RSPAMD_SERVER}
- ${RSPAMD_STAT}
- ${RSPAMD_MIME}
- ${CMAKE_CURRENT_BINARY_DIR}/modules.c
- ${PLUGINSSRC}
- "${RAGEL_ragel_smtp_addr_OUTPUTS}"
- "${RAGEL_ragel_newlines_strip_OUTPUTS}"
- "${RAGEL_ragel_content_type_OUTPUTS}"
- "${RAGEL_ragel_content_disposition_OUTPUTS}"
- "${RAGEL_ragel_rfc2047_OUTPUTS}"
- "${RAGEL_ragel_smtp_date_OUTPUTS}"
- "${RAGEL_ragel_smtp_ip_OUTPUTS}"
- ${BACKWARD_ENABLE})
-ELSE()
- ADD_LIBRARY(rspamd-server SHARED
- ${RSPAMD_CRYPTOBOX}
- ${RSPAMD_UTIL}
- ${RSPAMD_SERVER}
- ${RSPAMD_STAT}
- ${RSPAMD_MIME}
- ${RSPAMD_LUA}
- ${CMAKE_CURRENT_BINARY_DIR}/modules.c
- ${PLUGINSSRC}
- "${RAGEL_ragel_smtp_addr_OUTPUTS}"
- "${RAGEL_ragel_newlines_strip_OUTPUTS}"
- "${RAGEL_ragel_content_type_OUTPUTS}"
- "${RAGEL_ragel_content_disposition_OUTPUTS}"
- "${RAGEL_ragel_rfc2047_OUTPUTS}"
- "${RAGEL_ragel_smtp_date_OUTPUTS}"
- "${RAGEL_ragel_smtp_ip_OUTPUTS}"
- ${BACKWARD_ENABLE})
-ENDIF()
-
-FOREACH(_DEP ${LIBSERVER_DEPENDS})
- ADD_DEPENDENCIES(rspamd-server "${_DEP}")
-ENDFOREACH()
-
-TARGET_LINK_LIBRARIES(rspamd-server rspamd-http-parser)
-TARGET_LINK_LIBRARIES(rspamd-server rspamd-fpconv)
-TARGET_LINK_LIBRARIES(rspamd-server rspamd-cdb)
-TARGET_LINK_LIBRARIES(rspamd-server rspamd-lpeg)
-TARGET_LINK_LIBRARIES(rspamd-server lcbtrie)
-IF(SYSTEM_ZSTD MATCHES "OFF")
- TARGET_LINK_LIBRARIES(rspamd-server rspamd-zstd)
-ELSE()
- TARGET_LINK_LIBRARIES(rspamd-server zstd)
-ENDIF()
-TARGET_LINK_LIBRARIES(rspamd-server rspamd-simdutf)
-
-IF (ENABLE_CLANG_PLUGIN MATCHES "ON")
- ADD_DEPENDENCIES(rspamd-server rspamd-clang)
-ENDIF()
-
-IF (NOT WITH_LUAJIT)
- TARGET_LINK_LIBRARIES(rspamd-server rspamd-bit)
-ENDIF()
-
-IF (ENABLE_SNOWBALL MATCHES "ON")
- TARGET_LINK_LIBRARIES(rspamd-server stemmer)
-ENDIF()
-TARGET_LINK_LIBRARIES(rspamd-server rspamd-hiredis)
-
-IF (ENABLE_FANN MATCHES "ON")
- TARGET_LINK_LIBRARIES(rspamd-server fann)
-ENDIF ()
-
-IF (ENABLE_HYPERSCAN MATCHES "ON")
- TARGET_LINK_LIBRARIES(rspamd-server hs)
-ENDIF()
-
-IF(WITH_BLAS)
- TARGET_LINK_LIBRARIES(rspamd-server ${BLAS_REQUIRED_LIBRARIES})
-ENDIF()
-
-TARGET_LINK_LIBRARIES(rspamd-server ${RSPAMD_REQUIRED_LIBRARIES})
-ADD_BACKWARD(rspamd-server)
-
-ADD_EXECUTABLE(rspamd ${RSPAMDSRC} ${CMAKE_CURRENT_BINARY_DIR}/workers.c ${CMAKE_CURRENT_BINARY_DIR}/config.h)
-ADD_BACKWARD(rspamd)
-SET_TARGET_PROPERTIES(rspamd PROPERTIES LINKER_LANGUAGE CXX)
-SET_TARGET_PROPERTIES(rspamd-server PROPERTIES LINKER_LANGUAGE CXX)
-IF(NOT NO_TARGET_VERSIONS)
- SET_TARGET_PROPERTIES(rspamd PROPERTIES VERSION ${RSPAMD_VERSION})
-ENDIF()
-
-#TARGET_LINK_LIBRARIES(rspamd ${RSPAMD_REQUIRED_LIBRARIES})
-TARGET_LINK_LIBRARIES(rspamd rspamd-server)
-
-INSTALL(TARGETS rspamd RUNTIME DESTINATION bin)
-INSTALL(TARGETS rspamd-server LIBRARY DESTINATION ${RSPAMD_LIBDIR}) \ No newline at end of file
+# Function to generate module registrations
+function(generate_modules_list MODULE_LIST)
+ # Generate unique string for this build
+ set(MODULES_C "${CMAKE_CURRENT_BINARY_DIR}/modules.c")
+ file(WRITE "${MODULES_C}"
+ "#include \"rspamd.h\"\n")
+
+ # Process each module
+ foreach (MOD IN LISTS ${MODULE_LIST})
+ file(APPEND "${MODULES_C}" "extern module_t ${MOD}_module;\n")
+ endforeach ()
+
+ file(APPEND "${MODULES_C}" "\n\nmodule_t *modules[] = {\n")
+
+ foreach (MOD IN LISTS ${MODULE_LIST})
+ file(APPEND "${MODULES_C}" "&${MOD}_module,\n")
+ endforeach ()
+
+ file(APPEND "${MODULES_C}" "NULL\n};\n")
+
+ # Return the generated file path
+ set(MODULES_C_PATH "${MODULES_C}" PARENT_SCOPE)
+endfunction()
+
+# Function to generate worker registrations
+function(generate_workers_list WORKER_LIST)
+ set(WORKERS_C "${CMAKE_CURRENT_BINARY_DIR}/workers.c")
+ file(WRITE "${WORKERS_C}"
+ "#include \"rspamd.h\"\n")
+
+ # Process each worker
+ foreach (WRK IN LISTS ${WORKER_LIST})
+ file(APPEND "${WORKERS_C}" "extern worker_t ${WRK}_worker;\n")
+ endforeach ()
+
+ file(APPEND "${WORKERS_C}" "\n\nworker_t *workers[] = {\n")
+
+ foreach (WRK IN LISTS ${WORKER_LIST})
+ file(APPEND "${WORKERS_C}" "&${WRK}_worker,\n")
+ endforeach ()
+
+ file(APPEND "${WORKERS_C}" "NULL\n};\n")
+
+ # Return the generated file path
+ set(WORKERS_C_PATH "${WORKERS_C}" PARENT_SCOPE)
+endfunction()
+
+# Function to generate both modules and workers
+function(generate_registration_code MODULE_LIST WORKER_LIST)
+ generate_modules_list(${MODULE_LIST})
+ generate_workers_list(${WORKER_LIST})
+
+ # Set parent scope variables
+ set(MODULES_C_PATH ${MODULES_C_PATH} PARENT_SCOPE)
+ set(WORKERS_C_PATH ${WORKERS_C_PATH} PARENT_SCOPE)
+endfunction()
+
+# Configure Clang Plugin if enabled
+if (ENABLE_CLANG_PLUGIN)
+ set(CLANG_PLUGIN_FLAGS "-Xclang -load -Xclang ${CMAKE_CURRENT_BINARY_DIR}/../clang-plugin/librspamd-clang${CMAKE_SHARED_LIBRARY_SUFFIX} -Xclang -add-plugin -Xclang rspamd-ast")
+
+ # Apply to both C and C++ compiler flags
+ add_compile_options(${CLANG_PLUGIN_FLAGS})
+
+ # Add any extra clang plugins
+ if (CLANG_EXTRA_PLUGINS_LIBS)
+ foreach (lib ${CLANG_EXTRA_PLUGINS_LIBS})
+ add_compile_options("-Xclang" "-load" "-Xclang" "${lib}")
+ endforeach ()
+ endif ()
+
+ if (CLANG_EXTRA_PLUGINS)
+ foreach (plug ${CLANG_EXTRA_PLUGINS})
+ add_compile_options("-Xclang" "-add-plugin" "-Xclang" "${plug}")
+ endforeach ()
+ endif ()
+endif ()
+
+# Add subdirectories for components
+add_subdirectory(lua)
+add_subdirectory(libcryptobox)
+add_subdirectory(libutil)
+add_subdirectory(libserver)
+add_subdirectory(libmime)
+add_subdirectory(libstat)
+add_subdirectory(client)
+add_subdirectory(rspamadm)
+
+# Define source files
+set(RSPAMD_SOURCES
+ controller.c
+ fuzzy_storage.c
+ rspamd.c
+ worker.c
+ rspamd_proxy.c)
+
+set(PLUGIN_SOURCES
+ plugins/regexp.c
+ plugins/chartable.cxx
+ plugins/fuzzy_check.c
+ plugins/dkim_check.c
+ libserver/rspamd_control.c)
+
+# Define module and worker lists
+set(MODULES_LIST regexp chartable fuzzy_check dkim)
+set(WORKERS_LIST normal controller fuzzy rspamd_proxy)
+
+# Add hyperscan worker if enabled
+if (ENABLE_HYPERSCAN)
+ list(APPEND WORKERS_LIST hs_helper)
+ list(APPEND RSPAMD_SOURCES hs_helper.c)
+endif ()
+
+# Generate modules and workers registration code
+generate_registration_code(MODULES_LIST WORKERS_LIST)
+
+# Count the number of modules
+list(LENGTH PLUGIN_SOURCES RSPAMD_MODULES_NUM)
+
+# Configure Ragel for parsers
+set(RAGEL_DEPENDS
+ "${CMAKE_SOURCE_DIR}/src/ragel/smtp_address.rl"
+ "${CMAKE_SOURCE_DIR}/src/ragel/smtp_date.rl"
+ "${CMAKE_SOURCE_DIR}/src/ragel/smtp_ip.rl"
+ "${CMAKE_SOURCE_DIR}/src/ragel/smtp_base.rl"
+ "${CMAKE_SOURCE_DIR}/src/ragel/content_disposition.rl")
+
+# Generate parsers with Ragel
+ragel_target(ragel_smtp_addr
+ INPUTS ${CMAKE_SOURCE_DIR}/src/ragel/smtp_addr_parser.rl
+ DEPENDS ${RAGEL_DEPENDS}
+ COMPILE_FLAGS -T1
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/smtp_addr_parser.rl.c)
+
+ragel_target(ragel_content_disposition
+ INPUTS ${CMAKE_SOURCE_DIR}/src/ragel/content_disposition_parser.rl
+ DEPENDS ${RAGEL_DEPENDS}
+ COMPILE_FLAGS -G2
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/content_disposition.rl.c)
+
+ragel_target(ragel_rfc2047
+ INPUTS ${CMAKE_SOURCE_DIR}/src/ragel/rfc2047_parser.rl
+ DEPENDS ${RAGEL_DEPENDS}
+ COMPILE_FLAGS -G2
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/rfc2047.rl.c)
+
+ragel_target(ragel_smtp_date
+ INPUTS ${CMAKE_SOURCE_DIR}/src/ragel/smtp_date_parser.rl
+ DEPENDS ${RAGEL_DEPENDS}
+ COMPILE_FLAGS -G2
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/date_parser.rl.c)
+
+ragel_target(ragel_smtp_ip
+ INPUTS ${CMAKE_SOURCE_DIR}/src/ragel/smtp_ip_parser.rl
+ DEPENDS ${RAGEL_DEPENDS}
+ COMPILE_FLAGS -G2
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/ip_parser.rl.c)
+
+# Mark generated files correctly
+foreach (_gen ${LIBSERVER_GENERATED})
+ set_source_files_properties(${_gen} PROPERTIES GENERATED TRUE)
+endforeach ()
+
+# Collection of all generated Ragel outputs
+set(RAGEL_OUTPUTS
+ ${RAGEL_ragel_smtp_addr_OUTPUTS}
+ ${RAGEL_ragel_newlines_strip_OUTPUTS}
+ ${RAGEL_ragel_content_type_OUTPUTS}
+ ${RAGEL_ragel_content_disposition_OUTPUTS}
+ ${RAGEL_ragel_rfc2047_OUTPUTS}
+ ${RAGEL_ragel_smtp_date_OUTPUTS}
+ ${RAGEL_ragel_smtp_ip_OUTPUTS})
+
+# Common sources for rspamd-server
+set(SERVER_COMMON_SOURCES
+ ${RSPAMD_CRYPTOBOX}
+ ${RSPAMD_UTIL}
+ ${RSPAMD_LUA}
+ ${RSPAMD_SERVER}
+ ${RSPAMD_STAT}
+ ${RSPAMD_MIME}
+ ${MODULES_C_PATH}
+ ${PLUGIN_SOURCES}
+ ${RAGEL_OUTPUTS}
+ ${BACKWARD_ENABLE})
+
+# Build rspamd-server as static or shared library based on configuration
+if (ENABLE_STATIC)
+ add_library(rspamd-server STATIC ${SERVER_COMMON_SOURCES})
+else ()
+ add_library(rspamd-server SHARED ${SERVER_COMMON_SOURCES})
+endif ()
+
+# Set dependencies for rspamd-server
+foreach (_dep ${LIBSERVER_DEPENDS})
+ add_dependencies(rspamd-server "${_dep}")
+endforeach ()
+
+# Link dependencies
+target_link_libraries(rspamd-server
+ PRIVATE
+ rspamd-http-parser
+ rspamd-fpconv
+ rspamd-cdb
+ rspamd-lpeg
+ ottery
+ lcbtrie
+ rspamd-simdutf
+ rdns
+ ucl)
+
+# Handle xxhash dependency
+if (SYSTEM_XXHASH)
+ target_link_libraries(rspamd-server PUBLIC xxhash)
+else ()
+ target_link_libraries(rspamd-server PUBLIC rspamd-xxhash)
+endif ()
+
+# Handle zstd dependency
+if (SYSTEM_ZSTD)
+ target_link_libraries(rspamd-server PUBLIC zstd)
+else ()
+ target_link_libraries(rspamd-server PRIVATE rspamd-zstd)
+endif ()
+
+# Handle clang plugin dependency
+if (ENABLE_CLANG_PLUGIN)
+ add_dependencies(rspamd-server rspamd-clang)
+endif ()
+
+# Handle Lua JIT/Lua dependency
+if (NOT WITH_LUAJIT)
+ target_link_libraries(rspamd-server PRIVATE rspamd-bit)
+endif ()
+
+# Link additional optional dependencies
+if (ENABLE_SNOWBALL)
+ target_link_libraries(rspamd-server PRIVATE stemmer)
+endif ()
+
+target_link_libraries(rspamd-server PRIVATE rspamd-hiredis)
+
+if (ENABLE_FANN)
+ target_link_libraries(rspamd-server PRIVATE fann)
+endif ()
+
+if (ENABLE_HYPERSCAN)
+ target_link_libraries(rspamd-server PUBLIC hs)
+endif ()
+
+if (WITH_BLAS)
+ target_link_libraries(rspamd-server PRIVATE ${BLAS_REQUIRED_LIBRARIES})
+endif ()
+
+# Link all required system libraries
+target_link_libraries(rspamd-server PUBLIC ${RSPAMD_REQUIRED_LIBRARIES})
+
+# Add Backward support for stacktrace
+add_backward(rspamd-server)
+
+# Build main rspamd executable
+add_executable(rspamd
+ ${RSPAMD_SOURCES}
+ ${WORKERS_C_PATH}
+ ${CMAKE_CURRENT_BINARY_DIR}/config.h)
+
+# Configure rspamd executable
+add_backward(rspamd)
+set_target_properties(rspamd PROPERTIES LINKER_LANGUAGE CXX)
+set_target_properties(rspamd-server PROPERTIES LINKER_LANGUAGE CXX)
+
+if (NOT NO_TARGET_VERSIONS)
+ set_target_properties(rspamd PROPERTIES VERSION ${RSPAMD_VERSION})
+endif ()
+
+# Link rspamd executable with the server library
+target_link_libraries(rspamd PRIVATE rspamd-server)
+
+# Install targets
+install(TARGETS rspamd
+ RUNTIME
+ DESTINATION bin)
+
+install(TARGETS rspamd-server
+ LIBRARY
+ DESTINATION ${RSPAMD_LIBDIR})
diff --git a/src/controller.c b/src/controller.c
index 386448f93..22423e999 100644
--- a/src/controller.c
+++ b/src/controller.c
@@ -1,5 +1,5 @@
/*
- * Copyright 2024 Vsevolod Stakhov
+ * Copyright 2025 Vsevolod Stakhov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -979,12 +979,6 @@ rspamd_controller_handle_maps(struct rspamd_http_connection_entry *conn_ent,
if (bk->protocol == MAP_PROTO_FILE) {
editable = rspamd_controller_can_edit_map(bk);
-
- if (!editable && access(bk->uri, R_OK) == -1) {
- /* Skip unreadable and non-existing maps */
- continue;
- }
-
obj = ucl_object_typed_new(UCL_OBJECT);
ucl_object_insert_key(obj, ucl_object_fromint(bk->id),
"map", 0, false);
@@ -994,8 +988,34 @@ rspamd_controller_handle_maps(struct rspamd_http_connection_entry *conn_ent,
}
ucl_object_insert_key(obj, ucl_object_fromstring(bk->uri),
"uri", 0, false);
+ ucl_object_insert_key(obj, ucl_object_fromstring("file"),
+ "type", 0, false);
ucl_object_insert_key(obj, ucl_object_frombool(editable),
"editable", 0, false);
+ ucl_object_insert_key(obj, ucl_object_frombool(bk->shared->loaded),
+ "loaded", 0, false);
+ ucl_object_insert_key(obj, ucl_object_frombool(bk->shared->cached),
+ "cached", 0, false);
+ ucl_array_append(top, obj);
+ }
+ else {
+ obj = ucl_object_typed_new(UCL_OBJECT);
+ ucl_object_insert_key(obj, ucl_object_fromint(bk->id),
+ "map", 0, false);
+ if (map->description) {
+ ucl_object_insert_key(obj, ucl_object_fromstring(map->description),
+ "description", 0, false);
+ }
+ ucl_object_insert_key(obj, ucl_object_fromstring(bk->uri),
+ "uri", 0, false);
+ ucl_object_insert_key(obj, ucl_object_fromstring(rspamd_map_fetch_protocol_name(bk->protocol)),
+ "type", 0, false);
+ ucl_object_insert_key(obj, ucl_object_frombool(false),
+ "editable", 0, false);
+ ucl_object_insert_key(obj, ucl_object_frombool(bk->shared->loaded),
+ "loaded", 0, false);
+ ucl_object_insert_key(obj, ucl_object_frombool(bk->shared->cached),
+ "cached", 0, false);
ucl_array_append(top, obj);
}
}
@@ -1008,6 +1028,21 @@ rspamd_controller_handle_maps(struct rspamd_http_connection_entry *conn_ent,
return 0;
}
+gboolean
+rspamd_controller_map_traverse_callback(gconstpointer key, gconstpointer value, gsize _hits, gpointer ud)
+{
+ rspamd_fstring_t **target = (rspamd_fstring_t **) ud;
+
+ *target = rspamd_fstring_append(*target, key, strlen(key));
+
+ if (value) {
+ *target = rspamd_fstring_append(*target, " ", 1);
+ *target = rspamd_fstring_append(*target, value, strlen(value));
+ }
+ *target = rspamd_fstring_append(*target, "\n", 1);
+
+ return TRUE;
+}
/*
* Get map command handler:
* request: /getmap
@@ -1020,7 +1055,7 @@ rspamd_controller_handle_get_map(struct rspamd_http_connection_entry *conn_ent,
{
struct rspamd_controller_session *session = conn_ent->ud;
GList *cur;
- struct rspamd_map *map;
+ struct rspamd_map *map = NULL;
struct rspamd_map_backend *bk = NULL;
const rspamd_ftok_t *idstr;
struct stat st;
@@ -1054,7 +1089,7 @@ rspamd_controller_handle_get_map(struct rspamd_http_connection_entry *conn_ent,
PTR_ARRAY_FOREACH(map->backends, i, bk)
{
- if (bk->id == id && bk->protocol == MAP_PROTO_FILE) {
+ if (bk->id == id) {
found = TRUE;
break;
}
@@ -1069,32 +1104,53 @@ rspamd_controller_handle_get_map(struct rspamd_http_connection_entry *conn_ent,
return 0;
}
- if (stat(bk->uri, &st) == -1 || (fd = open(bk->uri, O_RDONLY)) == -1) {
+ if (bk->protocol == MAP_PROTO_FILE) {
+ if (stat(bk->uri, &st) == -1 || (fd = open(bk->uri, O_RDONLY)) == -1) {
+ reply = rspamd_http_new_message(HTTP_RESPONSE);
+ reply->date = time(NULL);
+ reply->code = 200;
+ }
+ else {
+
+ reply = rspamd_http_new_message(HTTP_RESPONSE);
+ reply->date = time(NULL);
+ reply->code = 200;
+
+ if (st.st_size > 0) {
+ if (!rspamd_http_message_set_body_from_fd(reply, fd)) {
+ close(fd);
+ rspamd_http_message_unref(reply);
+ msg_err_session("cannot read map %s: %s", bk->uri, strerror(errno));
+ rspamd_controller_send_error(conn_ent, 500, "Map read error");
+ return 0;
+ }
+ }
+ else {
+ rspamd_fstring_t *empty_body = rspamd_fstring_new_init("", 0);
+ rspamd_http_message_set_body_from_fstring_steal(reply, empty_body);
+ }
+
+ close(fd);
+ }
+ }
+ else if (bk->protocol == MAP_PROTO_STATIC) {
+ /* We can just traverse map and form reply */
reply = rspamd_http_new_message(HTTP_RESPONSE);
- reply->date = time(NULL);
reply->code = 200;
+ rspamd_fstring_t *map_body = rspamd_fstring_new();
+ rspamd_map_traverse(bk->map, rspamd_controller_map_traverse_callback, &map_body, FALSE);
+ rspamd_http_message_set_body_from_fstring_steal(reply, map_body);
}
- else {
-
+ else if (bk->shared->loaded) {
reply = rspamd_http_new_message(HTTP_RESPONSE);
- reply->date = time(NULL);
reply->code = 200;
-
- if (st.st_size > 0) {
- if (!rspamd_http_message_set_body_from_fd(reply, fd)) {
- close(fd);
- rspamd_http_message_unref(reply);
- msg_err_session("cannot read map %s: %s", bk->uri, strerror(errno));
- rspamd_controller_send_error(conn_ent, 500, "Map read error");
- return 0;
- }
- }
- else {
- rspamd_fstring_t *empty_body = rspamd_fstring_new_init("", 0);
- rspamd_http_message_set_body_from_fstring_steal(reply, empty_body);
- }
-
- close(fd);
+ rspamd_fstring_t *map_body = rspamd_fstring_new();
+ rspamd_map_traverse(bk->map, rspamd_controller_map_traverse_callback, &map_body, FALSE);
+ rspamd_http_message_set_body_from_fstring_steal(reply, map_body);
+ }
+ else {
+ reply = rspamd_http_new_message(HTTP_RESPONSE);
+ reply->code = 404;
}
rspamd_http_connection_reset(conn_ent->conn);
diff --git a/src/libserver/fuzzy_backend/fuzzy_backend_redis.c b/src/libserver/fuzzy_backend/fuzzy_backend_redis.c
index 27c663070..f150d48be 100644
--- a/src/libserver/fuzzy_backend/fuzzy_backend_redis.c
+++ b/src/libserver/fuzzy_backend/fuzzy_backend_redis.c
@@ -116,11 +116,9 @@ rspamd_redis_get_servers(struct rspamd_fuzzy_backend_redis *ctx,
res = *((struct upstream_list **) lua_touserdata(L, -1));
}
else {
- struct lua_logger_trace tr;
char outbuf[8192];
- memset(&tr, 0, sizeof(tr));
- lua_logger_out_type(L, -2, outbuf, sizeof(outbuf) - 1, &tr,
+ lua_logger_out(L, -2, outbuf, sizeof(outbuf),
LUA_ESCAPE_UNPRINTABLE);
msg_err("cannot get %s upstreams for Redis fuzzy storage %s; table content: %s",
diff --git a/src/libserver/maps/map.c b/src/libserver/maps/map.c
index 97130ad7c..51390f24b 100644
--- a/src/libserver/maps/map.c
+++ b/src/libserver/maps/map.c
@@ -339,6 +339,7 @@ http_map_finish(struct rspamd_http_connection *conn,
cbd->periodic->cur_backend = 0;
/* Reset cache, old cached data will be cleaned on timeout */
g_atomic_int_set(&data->cache->available, 0);
+ g_atomic_int_set(&bk->shared->loaded, 0);
data->cur_cache_cbd = NULL;
rspamd_map_process_periodic(cbd->periodic);
@@ -424,6 +425,8 @@ http_map_finish(struct rspamd_http_connection *conn,
* We know that a map is in the locked state
*/
g_atomic_int_set(&data->cache->available, 1);
+ g_atomic_int_set(&bk->shared->loaded, 1);
+ g_atomic_int_set(&bk->shared->cached, 0);
/* Store cached data */
rspamd_strlcpy(data->cache->shmem_name, cbd->shmem_data->shm_name,
sizeof(data->cache->shmem_name));
@@ -919,6 +922,8 @@ read_map_file(struct rspamd_map *map, struct file_map_data *data,
map->read_callback(NULL, 0, &periodic->cbdata, TRUE);
}
+ g_atomic_int_set(&bk->shared->loaded, 1);
+
return TRUE;
}
@@ -1003,6 +1008,7 @@ read_map_static(struct rspamd_map *map, struct static_map_data *data,
}
data->processed = TRUE;
+ g_atomic_int_set(&bk->shared->loaded, 1);
return TRUE;
}
@@ -1011,6 +1017,7 @@ static void
rspamd_map_periodic_dtor(struct map_periodic_cbdata *periodic)
{
struct rspamd_map *map;
+ struct rspamd_map_backend *bk;
map = periodic->map;
msg_debug_map("periodic dtor %p; need_modify=%d", periodic, periodic->need_modify);
@@ -1028,8 +1035,11 @@ rspamd_map_periodic_dtor(struct map_periodic_cbdata *periodic)
}
if (periodic->locked) {
- g_atomic_int_set(periodic->map->locked, 0);
- msg_debug_map("unlocked map %s", periodic->map->name);
+ if (periodic->cur_backend < map->backends->len) {
+ bk = (struct rspamd_map_backend *) g_ptr_array_index(map->backends, periodic->cur_backend);
+ g_atomic_int_set(&bk->shared->locked, 0);
+ msg_debug_map("unlocked map %s", map->name);
+ }
if (periodic->map->wrk->state == rspamd_worker_state_running) {
rspamd_map_schedule_periodic(periodic->map,
@@ -1438,6 +1448,9 @@ rspamd_map_read_cached(struct rspamd_map *map, struct rspamd_map_backend *bk,
map->read_callback(in, len, &periodic->cbdata, TRUE);
}
+ g_atomic_int_set(&bk->shared->loaded, 1);
+ g_atomic_int_set(&bk->shared->cached, 1);
+
munmap(in, mmap_len);
return TRUE;
@@ -1727,6 +1740,8 @@ rspamd_map_read_http_cached_file(struct rspamd_map *map,
struct tm tm;
char ncheck_buf[32], lm_buf[32];
+ g_atomic_int_set(&bk->shared->loaded, 1);
+ g_atomic_int_set(&bk->shared->cached, 1);
rspamd_localtime(map->next_check, &tm);
strftime(ncheck_buf, sizeof(ncheck_buf) - 1, "%Y-%m-%d %H:%M:%S", &tm);
rspamd_localtime(htdata->last_modified, &tm);
@@ -2027,8 +2042,20 @@ rspamd_map_process_periodic(struct map_periodic_cbdata *cbd)
map = cbd->map;
map->scheduled_check = NULL;
+ /* For each backend we need to check for modifications */
+ if (cbd->cur_backend >= cbd->map->backends->len) {
+ /* Last backend */
+ msg_debug_map("finished map: %d of %d", cbd->cur_backend,
+ cbd->map->backends->len);
+ MAP_RELEASE(cbd, "periodic");
+
+ return;
+ }
+
+ bk = g_ptr_array_index(map->backends, cbd->cur_backend);
+
if (!map->file_only && !cbd->locked) {
- if (!g_atomic_int_compare_and_exchange(cbd->map->locked,
+ if (!g_atomic_int_compare_and_exchange(&bk->shared->locked,
0, 1)) {
msg_debug_map(
"don't try to reread map %s as it is locked by other process, "
@@ -2040,7 +2067,7 @@ rspamd_map_process_periodic(struct map_periodic_cbdata *cbd)
return;
}
else {
- msg_debug_map("locked map %s", cbd->map->name);
+ msg_debug_map("locked map %s", map->name);
cbd->locked = TRUE;
}
}
@@ -2050,7 +2077,7 @@ rspamd_map_process_periodic(struct map_periodic_cbdata *cbd)
rspamd_map_schedule_periodic(cbd->map, RSPAMD_MAP_SCHEDULE_ERROR);
if (cbd->locked) {
- g_atomic_int_set(cbd->map->locked, 0);
+ g_atomic_int_set(&bk->shared->locked, 0);
cbd->locked = FALSE;
}
@@ -2064,19 +2091,7 @@ rspamd_map_process_periodic(struct map_periodic_cbdata *cbd)
return;
}
- /* For each backend we need to check for modifications */
- if (cbd->cur_backend >= cbd->map->backends->len) {
- /* Last backend */
- msg_debug_map("finished map: %d of %d", cbd->cur_backend,
- cbd->map->backends->len);
- MAP_RELEASE(cbd, "periodic");
-
- return;
- }
-
if (cbd->map->wrk && cbd->map->wrk->state == rspamd_worker_state_running) {
- 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 */
@@ -2781,9 +2796,8 @@ rspamd_map_parse_backend(struct rspamd_config *cfg, const char *map_line)
bk->data.sd = sdata;
}
- bk->id = rspamd_cryptobox_fast_hash_specific(RSPAMD_CRYPTOBOX_T1HA,
- bk->uri, strlen(bk->uri),
- 0xdeadbabe);
+ bk->shared = rspamd_mempool_alloc0_shared(cfg->cfg_pool,
+ sizeof(struct rspamd_map_shared_backend_data));
return bk;
@@ -2815,6 +2829,13 @@ rspamd_map_calculate_hash(struct rspamd_map *map)
rspamd_cryptobox_hash_init(&st, NULL, 0);
+ if (map->name) {
+ rspamd_cryptobox_hash_update(&st, map->name, strlen(map->name));
+ }
+ if (map->description) {
+ rspamd_cryptobox_hash_update(&st, map->description, strlen(map->description));
+ }
+
for (i = 0; i < map->backends->len; i++) {
bk = g_ptr_array_index(map->backends, i);
rspamd_cryptobox_hash_update(&st, bk->uri, strlen(bk->uri));
@@ -2823,6 +2844,26 @@ rspamd_map_calculate_hash(struct rspamd_map *map)
rspamd_cryptobox_hash_final(&st, cksum);
cksum_encoded = rspamd_encode_base32(cksum, sizeof(cksum), RSPAMD_BASE32_DEFAULT);
rspamd_strlcpy(map->tag, cksum_encoded, sizeof(map->tag));
+
+ for (i = 0; i < map->backends->len; i++) {
+ bk = g_ptr_array_index(map->backends, i);
+
+ /* Also update each backend */
+ rspamd_cryptobox_fast_hash_state_t hst;
+ rspamd_cryptobox_fast_hash_init(&hst, 0);
+ rspamd_cryptobox_fast_hash_update(&hst, bk->uri, strlen(bk->uri));
+ rspamd_cryptobox_fast_hash_update(&hst, map->tag, sizeof(map->tag));
+
+ if (bk->protocol == MAP_PROTO_STATIC) {
+ /* Static maps content is pre-defined */
+ rspamd_cryptobox_fast_hash_update(&hst, bk->data.sd->data,
+ bk->data.sd->len);
+ }
+
+ /* We use only 52 bits to be compatible with other numbers representation */
+ bk->id = rspamd_cryptobox_fast_hash_final(&hst) & ~(0xFFFULL << 52);
+ }
+
g_free(cksum_encoded);
}
@@ -2888,8 +2929,6 @@ rspamd_map_add(struct rspamd_config *cfg,
map->user_data = user_data;
map->cfg = cfg;
map->id = rspamd_random_uint64_fast();
- map->locked =
- rspamd_mempool_alloc0_shared(cfg->cfg_pool, sizeof(int));
map->backends = g_ptr_array_sized_new(1);
map->wrk = worker;
rspamd_mempool_add_destructor(cfg->cfg_pool, rspamd_ptr_array_free_hard,
@@ -2988,8 +3027,6 @@ rspamd_map_add_from_ucl(struct rspamd_config *cfg,
map->user_data = user_data;
map->cfg = cfg;
map->id = rspamd_random_uint64_fast();
- map->locked =
- rspamd_mempool_alloc0_shared(cfg->cfg_pool, sizeof(int));
map->backends = g_ptr_array_new();
map->wrk = worker;
map->no_file_read = (flags & RSPAMD_MAP_FILE_NO_READ);
@@ -3108,7 +3145,7 @@ rspamd_map_add_from_ucl(struct rspamd_config *cfg,
goto err;
}
- gboolean all_local = TRUE;
+ gboolean all_local = TRUE, all_loaded = TRUE;
PTR_ARRAY_FOREACH(map->backends, i, bk)
{
@@ -3127,9 +3164,8 @@ rspamd_map_add_from_ucl(struct rspamd_config *cfg,
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);
+ bk->data.sd->data = (unsigned char *) g_string_free(map_data, FALSE);
}
else {
g_string_free(map_data, TRUE);
@@ -3152,13 +3188,16 @@ rspamd_map_add_from_ucl(struct rspamd_config *cfg,
}
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);
+ bk->data.sd->data = (unsigned char *) g_string_free(map_data, FALSE);
}
}
else if (bk->protocol != MAP_PROTO_FILE) {
all_local = FALSE;
+ all_loaded = FALSE; /* Will be loaded later */
+ }
+ else {
+ all_loaded = FALSE; /* Will be loaded later (even for files) */
}
}
@@ -3167,6 +3206,11 @@ rspamd_map_add_from_ucl(struct rspamd_config *cfg,
cfg->map_file_watch_multiplier);
}
+ if (all_loaded) {
+ /* Static map */
+ g_atomic_int_set(&bk->shared->loaded, 1);
+ }
+
rspamd_map_calculate_hash(map);
msg_debug_map("added map from ucl");
diff --git a/src/libserver/maps/map_private.h b/src/libserver/maps/map_private.h
index d0b22fe36..66949f926 100644
--- a/src/libserver/maps/map_private.h
+++ b/src/libserver/maps/map_private.h
@@ -1,5 +1,5 @@
/*
- * Copyright 2024 Vsevolod Stakhov
+ * Copyright 2025 Vsevolod Stakhov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -54,6 +54,23 @@ enum fetch_proto {
MAP_PROTO_STATIC
};
+static const char *
+rspamd_map_fetch_protocol_name(enum fetch_proto proto)
+{
+ switch (proto) {
+ case MAP_PROTO_FILE:
+ return "file";
+ case MAP_PROTO_HTTP:
+ return "http";
+ case MAP_PROTO_HTTPS:
+ return "https";
+ case MAP_PROTO_STATIC:
+ return "static";
+ default:
+ return "unknown";
+ }
+}
+
/**
* Data specific to file maps
*/
@@ -76,7 +93,7 @@ struct rspamd_http_map_cached_cbdata {
time_t last_checked;
};
-struct rspamd_map_cachepoint {
+struct rspamd_http_map_cache {
int available;
gsize len;
time_t last_modified;
@@ -88,7 +105,7 @@ struct rspamd_map_cachepoint {
*/
struct http_map_data {
/* Shared cache data */
- struct rspamd_map_cachepoint *cache;
+ struct rspamd_http_map_cache *cache;
/* Non-shared for cache owner, used to cleanup cache */
struct rspamd_http_map_cached_cbdata *cur_cache_cbd;
char *userinfo;
@@ -117,14 +134,23 @@ union rspamd_map_backend_data {
struct rspamd_map;
+/*
+ * Shared between workers
+ */
+struct rspamd_map_shared_backend_data {
+ int locked;
+ int loaded;
+ int cached;
+};
struct rspamd_map_backend {
enum fetch_proto protocol;
gboolean is_signed;
gboolean is_compressed;
gboolean is_fallback;
+ struct rspamd_map_shared_backend_data *shared;
struct rspamd_map *map;
struct ev_loop *event_loop;
- uint32_t id;
+ uint64_t id;
struct rspamd_cryptobox_pubkey *trusted_pubkey;
union rspamd_map_backend_data data;
char *uri;
@@ -167,8 +193,6 @@ struct rspamd_map {
bool static_only; /* No need to check */
bool no_file_read; /* Do not read files */
bool seen; /* This map has already been watched or pre-loaded */
- /* Shared lock for temporary disabling of map reading (e.g. when this map is written by UI) */
- int *locked;
char tag[MEMPOOL_UID_LEN];
};
diff --git a/src/libserver/symcache/symcache_impl.cxx b/src/libserver/symcache/symcache_impl.cxx
index 4d17348c2..c0278cfc1 100644
--- a/src/libserver/symcache/symcache_impl.cxx
+++ b/src/libserver/symcache/symcache_impl.cxx
@@ -126,7 +126,7 @@ auto symcache::init() -> bool
}
else {
msg_err_cache("cannot register delayed dependency %s -> %s: "
- "destionation %s is missing",
+ "destination %s is missing",
delayed_dep.from.data(),
delayed_dep.to.data(), delayed_dep.to.data());
}
@@ -1338,4 +1338,4 @@ auto symcache::get_max_timeout(std::vector<std::pair<double, const cache_item *>
return accumulated_timeout;
}
-}// namespace rspamd::symcache \ No newline at end of file
+}// namespace rspamd::symcache
diff --git a/src/lua/lua_common.h b/src/lua/lua_common.h
index a29444394..5819da8cb 100644
--- a/src/lua/lua_common.h
+++ b/src/lua/lua_common.h
@@ -538,8 +538,7 @@ enum lua_logger_escape_type {
* @param len
* @return
*/
-gsize lua_logger_out_type(lua_State *L, int pos, char *outbuf,
- gsize len, struct lua_logger_trace *trace,
+gsize lua_logger_out(lua_State *L, int pos, char *outbuf, gsize len,
enum lua_logger_escape_type esc_type);
/**
diff --git a/src/lua/lua_config.c b/src/lua/lua_config.c
index 07ed58ad5..f52eae44f 100644
--- a/src/lua/lua_config.c
+++ b/src/lua/lua_config.c
@@ -1,5 +1,5 @@
/*
- * Copyright 2024 Vsevolod Stakhov
+ * Copyright 2025 Vsevolod Stakhov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -118,7 +118,7 @@ local function foo(task)
end
*/
/***
-* @method rspamd_config:radix_from_ucl(obj)
+* @method rspamd_config:radix_from_ucl(obj, description)
* Creates new embedded map of IP/mask addresses from object.
* @param {ucl} obj object
* @return {map} radix tree object
diff --git a/src/lua/lua_logger.c b/src/lua/lua_logger.c
index 004b82e72..8f2aa5be1 100644
--- a/src/lua/lua_logger.c
+++ b/src/lua/lua_logger.c
@@ -174,6 +174,11 @@ static const struct luaL_reg loggerlib_f[] = {
{"__tostring", rspamd_lua_class_tostring},
{NULL, NULL}};
+static gsize
+lua_logger_out_type(lua_State *L, int pos, char *outbuf,
+ gsize len, struct lua_logger_trace *trace,
+ enum lua_logger_escape_type esc_type);
+
static void
lua_common_log_line(GLogLevelFlags level,
lua_State *L,
@@ -203,23 +208,19 @@ lua_common_log_line(GLogLevelFlags level,
d.currentline);
}
- rspamd_common_log_function(NULL,
- level,
- module,
- uid,
- func_buf,
- "%s",
- msg);
+ p = func_buf;
}
else {
- rspamd_common_log_function(NULL,
- level,
- module,
- uid,
- G_STRFUNC,
- "%s",
- msg);
+ p = (char *) G_STRFUNC;
}
+
+ rspamd_common_log_function(NULL,
+ level,
+ module,
+ uid,
+ p,
+ "%s",
+ msg);
}
/*** Logger interface ***/
@@ -279,105 +280,139 @@ lua_logger_char_safe(int t, unsigned int esc_type)
return true;
}
+/* Could return negative value in case of wrong argument number */
+static glong
+lua_logger_log_format_str(lua_State *L, int offset, char *logbuf, gsize remain,
+ const char *fmt,
+ enum lua_logger_escape_type esc_type)
+{
+ const char *c;
+ gsize r;
+ int digit;
+
+ char *d = logbuf;
+ unsigned int arg_num, cur_arg = 0, arg_max = lua_gettop(L) - offset;
+
+ while (remain > 1 && *fmt) {
+ if (*fmt == '%') {
+ ++fmt;
+ c = fmt;
+ if (*fmt == 's') {
+ ++fmt;
+ ++cur_arg;
+ } else {
+ arg_num = 0;
+ while ((digit = g_ascii_digit_value(*fmt)) >= 0) {
+ ++fmt;
+ arg_num = arg_num * 10 + digit;
+ if (arg_num >= 100) {
+ /* Avoid ridiculously large numbers */
+ fmt = c;
+ break;
+ }
+ }
+
+ if (fmt > c) {
+ /* Update the current argument */
+ cur_arg = arg_num;
+ }
+ }
+
+ if (fmt > c) {
+ if (cur_arg < 1 || cur_arg > arg_max) {
+ *d = 0;
+ return -((glong) cur_arg + 1); /* wrong argument number */
+ }
+
+ r = lua_logger_out(L, offset + cur_arg, d, remain, esc_type);
+ g_assert(r < remain);
+ remain -= r;
+ d += r;
+ continue;
+ }
+
+ /* Copy % */
+ --fmt;
+ }
+
+ *d++ = *fmt++;
+ --remain;
+ }
+
+ *d = 0;
+
+ return d - logbuf;
+}
+
static gsize
lua_logger_out_str(lua_State *L, int pos,
char *outbuf, gsize len,
- struct lua_logger_trace *trace,
enum lua_logger_escape_type esc_type)
{
- gsize slen, flen;
- const char *str = lua_tolstring(L, pos, &slen);
static const char hexdigests[16] = "0123456789abcdef";
- gsize r = 0, s;
+ gsize slen;
+ const unsigned char *str = lua_tolstring(L, pos, &slen);
+ unsigned char c;
+ char *out = outbuf;
if (str) {
- gboolean normal = TRUE;
- flen = MIN(slen, len - 1);
-
- for (r = 0; r < flen; r++) {
- if (!lua_logger_char_safe(str[r], esc_type)) {
- normal = FALSE;
- break;
+ while (slen > 0 && len > 1) {
+ c = *str++;
+ if (lua_logger_char_safe(c, esc_type)) {
+ *out++ = c;
}
- }
-
- if (normal) {
- r = rspamd_strlcpy(outbuf, str, flen + 1);
- }
- else {
- /* Need to escape non-printed characters */
- r = 0;
- s = 0;
-
- while (slen > 0 && len > 1) {
- if (!lua_logger_char_safe(str[s], esc_type)) {
- if (len >= 3) {
- outbuf[r++] = '\\';
- outbuf[r++] = hexdigests[((str[s] >> 4) & 0xF)];
- outbuf[r++] = hexdigests[((str[s]) & 0xF)];
-
- len -= 2;
- }
- else {
- outbuf[r++] = '?';
- }
- }
- else {
- outbuf[r++] = str[s];
- }
-
- s++;
- slen--;
- len--;
+ else if (len > 3) {
+ /* Need to escape non-printed characters */
+ *out++ = '\\';
+ *out++ = hexdigests[c >> 4];
+ *out++ = hexdigests[c & 0xF];
+ len -= 2;
}
-
- outbuf[r] = '\0';
+ else {
+ *out++ = '?';
+ }
+ --slen;
+ --len;
}
}
+ *out = 0;
- return r;
+ return out - outbuf;
}
static gsize
-lua_logger_out_num(lua_State *L, int pos, char *outbuf, gsize len,
- struct lua_logger_trace *trace)
+lua_logger_out_num(lua_State *L, int pos, char *outbuf, gsize len)
{
double num = lua_tonumber(L, pos);
- glong inum;
- gsize r = 0;
+ glong inum = (glong) num;
- if ((double) (glong) num == num) {
- inum = num;
- r = rspamd_snprintf(outbuf, len + 1, "%l", inum);
- }
- else {
- r = rspamd_snprintf(outbuf, len + 1, "%f", num);
+ if ((double) inum == num) {
+ return rspamd_snprintf(outbuf, len, "%l", inum);
}
- return r;
+ return rspamd_snprintf(outbuf, len, "%f", num);
}
static gsize
-lua_logger_out_boolean(lua_State *L, int pos, char *outbuf, gsize len,
- struct lua_logger_trace *trace)
+lua_logger_out_boolean(lua_State *L, int pos, char *outbuf, gsize len)
{
gboolean val = lua_toboolean(L, pos);
- gsize r = 0;
-
- r = rspamd_strlcpy(outbuf, val ? "true" : "false", len + 1);
- return r;
+ return rspamd_snprintf(outbuf, len, val ? "true" : "false");
}
static gsize
-lua_logger_out_userdata(lua_State *L, int pos, char *outbuf, gsize len,
- struct lua_logger_trace *trace)
+lua_logger_out_userdata(lua_State *L, int pos, char *outbuf, gsize len)
{
- int r = 0, top;
+ gsize r = 0;
+ int top;
const char *str = NULL;
gboolean converted_to_str = FALSE;
top = lua_gettop(L);
+ if (pos < 0) {
+ pos += top + 1; /* Convert to absolute */
+ }
if (!lua_getmetatable(L, pos)) {
return 0;
@@ -396,26 +431,17 @@ lua_logger_out_userdata(lua_State *L, int pos, char *outbuf, gsize len,
if (lua_isfunction(L, -1)) {
lua_pushvalue(L, pos);
- if (lua_pcall(L, 1, 1, 0) != 0) {
- lua_settop(L, top);
-
- return 0;
- }
-
- str = lua_tostring(L, -1);
-
- if (str) {
- r = rspamd_snprintf(outbuf, len, "%s", str);
+ if (lua_pcall(L, 1, 1, 0) == 0) {
+ str = lua_tostring(L, -1);
+ if (str) {
+ r = rspamd_snprintf(outbuf, len, "%s", str);
+ }
}
-
- lua_settop(L, top);
-
- return r;
}
}
lua_settop(L, top);
- return 0;
+ return r;
}
lua_pushstring(L, "__tostring");
@@ -460,12 +486,12 @@ lua_logger_out_userdata(lua_State *L, int pos, char *outbuf, gsize len,
return r;
}
-#define MOVE_BUF(d, remain, r) \
+#define MOVE_BUF(d, remain, r) \
(d) += (r); \
(remain) -= (r); \
- if ((remain) == 0) { \
- lua_settop(L, old_top); \
- break; \
+ if ((remain) <= 1) { \
+ lua_settop(L, top); \
+ goto table_oob; \
}
static gsize
@@ -473,169 +499,154 @@ lua_logger_out_table(lua_State *L, int pos, char *outbuf, gsize len,
struct lua_logger_trace *trace,
enum lua_logger_escape_type esc_type)
{
- char *d = outbuf;
- gsize remain = len, r;
+ char *d = outbuf, *str;
+ gsize remain = len;
+ glong r;
gboolean first = TRUE;
gconstpointer self = NULL;
- int i, tpos, last_seq = -1, old_top;
+ int i, last_seq = 0, top;
+ double num;
+ glong inum;
- if (!lua_istable(L, pos) || remain == 0) {
- return 0;
- }
+ /* Type and length checks are done in logger_out_type() */
- old_top = lua_gettop(L);
self = lua_topointer(L, pos);
/* Check if we have seen this pointer */
for (i = 0; i < TRACE_POINTS; i++) {
if (trace->traces[i] == self) {
- r = rspamd_snprintf(d, remain + 1, "ref(%p)", self);
-
- d += r;
-
- return (d - outbuf);
+ if ((trace->cur_level + TRACE_POINTS - 1) % TRACE_POINTS == i) {
+ return rspamd_snprintf(d, remain, "__self");
+ }
+ return rspamd_snprintf(d, remain, "ref(%p)", self);
}
}
trace->traces[trace->cur_level % TRACE_POINTS] = self;
+ ++trace->cur_level;
- lua_pushvalue(L, pos);
- r = rspamd_snprintf(d, remain + 1, "{");
- remain -= r;
- d += r;
+ top = lua_gettop(L);
+ if (pos < 0) {
+ pos += top + 1; /* Convert to absolute */
+ }
+
+ r = rspamd_snprintf(d, remain, "{");
+ MOVE_BUF(d, remain, r);
/* Get numeric keys (ipairs) */
for (i = 1;; i++) {
- lua_rawgeti(L, -1, i);
+ lua_rawgeti(L, pos, i);
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
+ last_seq = i;
break;
}
- last_seq = i;
-
- if (!first) {
- r = rspamd_snprintf(d, remain + 1, ", ");
- MOVE_BUF(d, remain, r);
+ if (first) {
+ first = FALSE;
+ str = "[%d] = ";
+ } else {
+ str = ", [%d] = ";
}
-
- r = rspamd_snprintf(d, remain + 1, "[%d] = ", i);
+ r = rspamd_snprintf(d, remain, str, i);
MOVE_BUF(d, remain, r);
- tpos = lua_gettop(L);
- if (lua_topointer(L, tpos) == self) {
- r = rspamd_snprintf(d, remain + 1, "__self");
- }
- else {
- r = lua_logger_out_type(L, tpos, d, remain, trace, esc_type);
- }
+ r = lua_logger_out_type(L, -1, d, remain, trace, esc_type);
MOVE_BUF(d, remain, r);
- first = FALSE;
lua_pop(L, 1);
}
/* Get string keys (pairs) */
- for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) {
+ for (lua_pushnil(L); lua_next(L, pos); lua_pop(L, 1)) {
/* 'key' is at index -2 and 'value' is at index -1 */
- if (lua_type(L, -2) == LUA_TNUMBER) {
- if (last_seq > 0) {
- lua_pushvalue(L, -2);
- if (lua_tonumber(L, -1) <= last_seq + 1) {
- lua_pop(L, 1);
+ /* Preserve key */
+ lua_pushvalue(L, -2);
+ if (last_seq > 0) {
+ if (lua_type(L, -1) == LUA_TNUMBER) {
+ num = lua_tonumber(L, -1); /* no conversion here */
+ inum = (glong) num;
+ if ((double) inum == num && inum > 0 && inum < last_seq) {
/* Already seen */
+ lua_pop(L, 1);
continue;
}
-
- lua_pop(L, 1);
}
}
- if (!first) {
- r = rspamd_snprintf(d, remain + 1, ", ");
- MOVE_BUF(d, remain, r);
+ if (first) {
+ first = FALSE;
+ str = "[%2] = %1";
+ } else {
+ str = ", [%2] = %1";
}
-
- /* Preserve key */
- lua_pushvalue(L, -2);
- r = rspamd_snprintf(d, remain + 1, "[%s] = ",
- lua_tostring(L, -1));
- lua_pop(L, 1); /* Remove key */
- MOVE_BUF(d, remain, r);
- tpos = lua_gettop(L);
-
- if (lua_topointer(L, tpos) == self) {
- r = rspamd_snprintf(d, remain + 1, "__self");
- }
- else {
- r = lua_logger_out_type(L, tpos, d, remain, trace, esc_type);
+ r = lua_logger_log_format_str(L, top + 1, d, remain, str, esc_type);
+ if (r < 0) {
+ /* should not happen */
+ goto table_oob;
}
MOVE_BUF(d, remain, r);
- first = FALSE;
+ /* Remove key */
+ lua_pop(L, 1);
}
- lua_settop(L, old_top);
-
- r = rspamd_snprintf(d, remain + 1, "}");
+ r = rspamd_snprintf(d, remain, "}");
d += r;
+table_oob:
+ --trace->cur_level;
+
return (d - outbuf);
}
#undef MOVE_BUF
-gsize lua_logger_out_type(lua_State *L, int pos,
+static gsize
+lua_logger_out_type(lua_State *L, int pos,
char *outbuf, gsize len,
struct lua_logger_trace *trace,
enum lua_logger_escape_type esc_type)
{
- int type;
- gsize r = 0;
-
if (len == 0) {
return 0;
}
- type = lua_type(L, pos);
- trace->cur_level++;
+ int type = lua_type(L, pos);
switch (type) {
case LUA_TNUMBER:
- r = lua_logger_out_num(L, pos, outbuf, len, trace);
- break;
+ return lua_logger_out_num(L, pos, outbuf, len);
case LUA_TBOOLEAN:
- r = lua_logger_out_boolean(L, pos, outbuf, len, trace);
- break;
+ return lua_logger_out_boolean(L, pos, outbuf, len);
case LUA_TTABLE:
- r = lua_logger_out_table(L, pos, outbuf, len, trace, esc_type);
- break;
+ return lua_logger_out_table(L, pos, outbuf, len, trace, esc_type);
case LUA_TUSERDATA:
- r = lua_logger_out_userdata(L, pos, outbuf, len, trace);
- break;
+ return lua_logger_out_userdata(L, pos, outbuf, len);
case LUA_TFUNCTION:
- r = rspamd_snprintf(outbuf, len + 1, "function");
- break;
+ return rspamd_snprintf(outbuf, len, "function");
case LUA_TLIGHTUSERDATA:
- r = rspamd_snprintf(outbuf, len + 1, "0x%p", lua_topointer(L, pos));
- break;
+ return rspamd_snprintf(outbuf, len, "0x%p", lua_topointer(L, pos));
case LUA_TNIL:
- r = rspamd_snprintf(outbuf, len + 1, "nil");
- break;
+ return rspamd_snprintf(outbuf, len, "nil");
case LUA_TNONE:
- r = rspamd_snprintf(outbuf, len + 1, "no value");
- break;
- default:
- /* Try to push everything as string using tostring magic */
- r = lua_logger_out_str(L, pos, outbuf, len, trace, esc_type);
- break;
+ return rspamd_snprintf(outbuf, len, "no value");
}
- trace->cur_level--;
+ /* Try to push everything as string using tostring magic */
+ return lua_logger_out_str(L, pos, outbuf, len, esc_type);
+}
- return r;
+gsize lua_logger_out(lua_State *L, int pos,
+ char *outbuf, gsize len,
+ enum lua_logger_escape_type esc_type)
+{
+ struct lua_logger_trace tr;
+ memset(&tr, 0, sizeof(tr));
+
+ return lua_logger_out_type(L, pos, outbuf, len, &tr, esc_type);
}
static const char *
@@ -731,72 +742,16 @@ static gboolean
lua_logger_log_format(lua_State *L, int fmt_pos, gboolean is_string,
char *logbuf, gsize remain)
{
- char *d;
- const char *s, *c;
- gsize r;
- unsigned int arg_num, arg_max, cur_arg;
- struct lua_logger_trace tr;
- int digit;
-
- s = lua_tostring(L, fmt_pos);
- if (s == NULL) {
+ const char *fmt = lua_tostring(L, fmt_pos);
+ if (fmt == NULL) {
return FALSE;
}
- arg_max = (unsigned int) lua_gettop(L) - fmt_pos;
- d = logbuf;
- cur_arg = 0;
-
- while (remain > 0 && *s) {
- if (*s == '%') {
- ++s;
- c = s;
- if (*s == 's') {
- ++s;
- ++cur_arg;
- } else {
- arg_num = 0;
- while ((digit = g_ascii_digit_value(*s)) >= 0) {
- ++s;
- arg_num = arg_num * 10 + digit;
- if (arg_num >= 100) {
- /* Avoid ridiculously large numbers */
- s = c;
- break;
- }
- }
-
- if (s > c) {
- /* Update the current argument */
- cur_arg = arg_num;
- }
- }
-
- if (s > c) {
- if (cur_arg < 1 || cur_arg > arg_max) {
- msg_err("wrong argument number: %ud", cur_arg);
- return FALSE;
- }
-
- memset(&tr, 0, sizeof(tr));
- r = lua_logger_out_type(L, fmt_pos + cur_arg, d, remain, &tr,
- is_string ? LUA_ESCAPE_UNPRINTABLE : LUA_ESCAPE_LOG);
- g_assert(r <= remain);
- remain -= r;
- d += r;
- continue;
- }
-
- /* Copy % */
- --s;
- }
-
- *d++ = *s++;
- --remain;
+ glong ret = lua_logger_log_format_str(L, fmt_pos, logbuf, remain, fmt, is_string ? LUA_ESCAPE_UNPRINTABLE : LUA_ESCAPE_LOG);
+ if (ret < 0) {
+ msg_err("wrong argument number: %ud", -((int) ret + 1));
+ return FALSE;
}
-
- *d = '\0';
-
return TRUE;
}
@@ -808,15 +763,10 @@ lua_logger_do_log(lua_State *L,
{
char logbuf[RSPAMD_LOGBUF_SIZE - 128];
const char *uid = NULL;
- int fmt_pos = start_pos;
int ret;
- GError *err = NULL;
- if (lua_type(L, start_pos) == LUA_TSTRING) {
- fmt_pos = start_pos;
- }
- else if (lua_type(L, start_pos) == LUA_TUSERDATA) {
- fmt_pos = start_pos + 1;
+ if (lua_type(L, start_pos) == LUA_TUSERDATA) {
+ GError *err = NULL;
uid = lua_logger_get_id(L, start_pos, &err);
@@ -830,15 +780,17 @@ lua_logger_do_log(lua_State *L,
return ret;
}
+
+ ++start_pos;
}
- else {
+
+ if (lua_type(L, start_pos) != LUA_TSTRING) {
/* Bad argument type */
return luaL_error(L, "bad format string type: %s",
lua_typename(L, lua_type(L, start_pos)));
}
- ret = lua_logger_log_format(L, fmt_pos, is_string,
- logbuf, sizeof(logbuf) - 1);
+ ret = lua_logger_log_format(L, start_pos, is_string, logbuf, sizeof(logbuf));
if (ret) {
if (is_string) {
@@ -849,12 +801,9 @@ lua_logger_do_log(lua_State *L,
lua_common_log_line(level, L, logbuf, uid, "lua", 1);
}
}
- else {
- if (is_string) {
- lua_pushnil(L);
-
- return 1;
- }
+ else if (is_string) {
+ lua_pushnil(L);
+ return 1;
}
return 0;
@@ -917,11 +866,11 @@ lua_logger_logx(lua_State *L)
if (uid && modname) {
if (lua_type(L, 4) == LUA_TSTRING) {
- ret = lua_logger_log_format(L, 4, FALSE, logbuf, sizeof(logbuf) - 1);
+ ret = lua_logger_log_format(L, 4, FALSE, logbuf, sizeof(logbuf));
}
else if (lua_type(L, 4) == LUA_TNUMBER) {
stack_pos = lua_tonumber(L, 4);
- ret = lua_logger_log_format(L, 5, FALSE, logbuf, sizeof(logbuf) - 1);
+ ret = lua_logger_log_format(L, 5, FALSE, logbuf, sizeof(logbuf));
}
else {
return luaL_error(L, "invalid argument on pos 4");
@@ -959,11 +908,11 @@ lua_logger_debugm(lua_State *L)
if (uid && module) {
if (lua_type(L, 3) == LUA_TSTRING) {
- ret = lua_logger_log_format(L, 3, FALSE, logbuf, sizeof(logbuf) - 1);
+ ret = lua_logger_log_format(L, 3, FALSE, logbuf, sizeof(logbuf));
}
else if (lua_type(L, 3) == LUA_TNUMBER) {
stack_pos = lua_tonumber(L, 3);
- ret = lua_logger_log_format(L, 4, FALSE, logbuf, sizeof(logbuf) - 1);
+ ret = lua_logger_log_format(L, 4, FALSE, logbuf, sizeof(logbuf));
}
else {
return luaL_error(L, "invalid argument on pos 3");
diff --git a/src/lua/lua_map.c b/src/lua/lua_map.c
index 062613bd7..5f55ece06 100644
--- a/src/lua/lua_map.c
+++ b/src/lua/lua_map.c
@@ -1,5 +1,5 @@
/*
- * Copyright 2024 Vsevolod Stakhov
+ * Copyright 2025 Vsevolod Stakhov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -319,6 +319,11 @@ int lua_config_radix_from_ucl(lua_State *L)
ucl_object_insert_key(fake_obj, ucl_object_fromstring("static"),
"url", 0, false);
+ if (lua_type(L, 3) == LUA_TSTRING) {
+ ucl_object_insert_key(fake_obj, ucl_object_fromstring(lua_tostring(L, 3)),
+ "description", 0, false);
+ }
+
if ((m = rspamd_map_add_from_ucl(cfg, fake_obj, "static radix map",
rspamd_radix_read,
rspamd_radix_fin,
diff --git a/src/plugins/lua/contextal.lua b/src/plugins/lua/contextal.lua
new file mode 100644
index 000000000..f6202781a
--- /dev/null
+++ b/src/plugins/lua/contextal.lua
@@ -0,0 +1,332 @@
+--[[
+Copyright (c) 2025, Vsevolod Stakhov <vsevolod@rspamd.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 E = {}
+local N = 'contextal'
+
+if confighelp then
+ return
+end
+
+local opts = rspamd_config:get_all_opt(N)
+if not opts then
+ return
+end
+
+local lua_redis = require "lua_redis"
+local lua_util = require "lua_util"
+local redis_cache = require "lua_cache"
+local rspamd_http = require "rspamd_http"
+local rspamd_logger = require "rspamd_logger"
+local rspamd_util = require "rspamd_util"
+local ts = require("tableshape").types
+local ucl = require "ucl"
+
+local cache_context, redis_params
+
+local contextal_actions = {
+ ['ALERT'] = true,
+ ['ALLOW'] = true,
+ ['BLOCK'] = true,
+ ['QUARANTINE'] = true,
+ ['SPAM'] = true,
+}
+
+local config_schema = lua_redis.enrich_schema {
+ action_symbol_prefix = ts.string:is_optional(),
+ base_url = ts.string:is_optional(),
+ cache_prefix = ts.string:is_optional(),
+ cache_timeout = ts.number:is_optional(),
+ cache_ttl = ts.number:is_optional(),
+ custom_actions = ts.array_of(ts.string):is_optional(),
+ defer_if_no_result = ts.boolean:is_optional(),
+ defer_message = ts.string:is_optional(),
+ enabled = ts.boolean:is_optional(),
+ http_timeout = ts.number:is_optional(),
+ request_ttl = ts.number:is_optional(),
+ submission_symbol = ts.string:is_optional(),
+}
+
+local settings = {
+ action_symbol_prefix = 'CONTEXTAL_ACTION',
+ base_url = 'http://localhost:8080',
+ cache_prefix = 'CXAL',
+ cache_timeout = 5,
+ cache_ttl = 3600,
+ custom_actions = {},
+ defer_if_no_result = false,
+ defer_message = 'Awaiting deep scan - try again later',
+ http_timeout = 2,
+ request_ttl = 4,
+ submission_symbol = 'CONTEXTAL_SUBMIT',
+}
+
+local static_boundary = rspamd_util.random_hex(32)
+local use_request_ttl = true
+
+local function maybe_defer(task, obj)
+ if settings.defer_if_no_result and not ((obj or E)[1] or E).actions then
+ task:set_pre_result('soft reject', settings.defer_message)
+ end
+end
+
+local function process_actions(task, obj, is_cached)
+ for _, match in ipairs((obj[1] or E).actions or E) do
+ local act = match.action
+ local scenario = match.scenario
+ if not (act and scenario) then
+ rspamd_logger.err(task, 'bad result: %s', match)
+ elseif contextal_actions[act] then
+ task:insert_result(settings.action_symbol_prefix .. '_' .. act, 1.0, scenario)
+ else
+ rspamd_logger.err(task, 'unknown action: %s', act)
+ end
+ end
+
+ if not cache_context or is_cached then
+ maybe_defer(task, obj)
+ return
+ end
+
+ local cache_obj
+ if (obj[1] or E).actions then
+ cache_obj = {[1] = {["actions"] = obj[1].actions}}
+ else
+ local work_id = task:get_mempool():get_variable('contextal_work_id', 'string')
+ if work_id then
+ cache_obj = {[1] = {["work_id"] = work_id}}
+ else
+ rspamd_logger.err(task, 'no work id found in mempool')
+ return
+ end
+ end
+
+ redis_cache.cache_set(task,
+ task:get_digest(),
+ cache_obj,
+ cache_context)
+
+ maybe_defer(task, obj)
+end
+
+local function process_cached(task, obj)
+ if (obj[1] or E).actions then
+ task:disable_symbol(settings.action_symbol_prefix)
+ return process_actions(task, obj, true)
+ elseif (obj[1] or E).work_id then
+ task:get_mempool():set_variable('contextal_work_id', obj[1].work_id)
+ else
+ rspamd_logger.err(task, 'bad result (cached): %s', obj)
+ end
+end
+
+local function action_cb(task)
+ local work_id = task:get_mempool():get_variable('contextal_work_id', 'string')
+ if not work_id then
+ rspamd_logger.err(task, 'no work id found in mempool')
+ return
+ end
+
+ local function http_callback(err, code, body, hdrs)
+ if err then
+ rspamd_logger.err(task, 'http error: %s', err)
+ maybe_defer(task)
+ return
+ end
+ if code ~= 200 then
+ rspamd_logger.err(task, 'bad http code: %s', code)
+ maybe_defer(task)
+ return
+ end
+ local parser = ucl.parser()
+ local _, parse_err = parser:parse_string(body)
+ if parse_err then
+ rspamd_logger.err(task, 'cannot parse JSON: %s', err)
+ maybe_defer(task)
+ return
+ end
+ local obj = parser:get_object()
+ return process_actions(task, obj, false)
+ end
+
+ rspamd_http.request({
+ task = task,
+ url = settings.actions_url .. work_id,
+ callback = http_callback,
+ timeout = settings.http_timeout,
+ gzip = settings.gzip,
+ keepalive = settings.keepalive,
+ no_ssl_verify = settings.no_ssl_verify,
+ })
+end
+
+local function submit(task)
+
+ local function http_callback(err, code, body, hdrs)
+ if err then
+ rspamd_logger.err(task, 'http error: %s', err)
+ maybe_defer(task)
+ return
+ end
+ if code ~= 201 then
+ rspamd_logger.err(task, 'bad http code: %s', code)
+ maybe_defer(task)
+ return
+ end
+ local parser = ucl.parser()
+ local _, parse_err = parser:parse_string(body)
+ if parse_err then
+ rspamd_logger.err(task, 'cannot parse JSON: %s', err)
+ maybe_defer(task)
+ return
+ end
+ local obj = parser:get_object()
+ local work_id = obj.work_id
+ if work_id then
+ task:get_mempool():set_variable('contextal_work_id', work_id)
+ end
+ task:insert_result(settings.submission_symbol, 1.0,
+ string.format('work_id=%s', work_id or 'nil'))
+ task:add_timer(settings.request_ttl, action_cb)
+ end
+
+ local req = {
+ object_data = {['data'] = task:get_content()},
+ }
+ if settings.request_ttl then
+ req.ttl = {['data'] = tostring(settings.request_ttl)}
+ end
+ if settings.max_recursion then
+ req.maxrec = {['data'] = tostring(settings.max_recursion)}
+ end
+ rspamd_http.request({
+ task = task,
+ url = settings.submit_url,
+ body = lua_util.table_to_multipart_body(req, static_boundary),
+ callback = http_callback,
+ headers = {
+ ['Content-Type'] = string.format('multipart/form-data; boundary="%s"', static_boundary)
+ },
+ timeout = settings.http_timeout,
+ gzip = settings.gzip,
+ keepalive = settings.keepalive,
+ no_ssl_verify = settings.no_ssl_verify,
+ })
+end
+
+local function cache_hit(task, err, data)
+ if err then
+ rspamd_logger.err(task, 'error getting cache: %s', err)
+ else
+ process_cached(task, data)
+ end
+end
+
+local function submit_cb(task)
+ if cache_context then
+ redis_cache.cache_get(task,
+ task:get_digest(),
+ cache_context,
+ settings.cache_timeout,
+ submit,
+ cache_hit
+ )
+ else
+ submit(task)
+ end
+end
+
+local function set_url_path(base, path)
+ local slash = base:sub(#base) == '/' and '' or '/'
+ return base .. slash .. path
+end
+
+settings = lua_util.override_defaults(settings, opts)
+
+local res, err = config_schema:transform(settings)
+if not res then
+ rspamd_logger.warnx(rspamd_config, 'plugin %s is misconfigured: %s', N, err)
+ local err_msg = string.format("schema error: %s", res)
+ lua_util.config_utils.push_config_error(N, err_msg)
+ lua_util.disable_module(N, "failed", err_msg)
+ return
+end
+
+for _, k in ipairs(settings.custom_actions) do
+ contextal_actions[k] = true
+end
+
+if not settings.base_url then
+ if not (settings.submit_url and settings.actions_url) then
+ rspamd_logger.err(rspamd_config, 'no URL configured for contextal')
+ lua_util.disable_module(N, 'config')
+ return
+ end
+else
+ if not settings.submit_url then
+ settings.submit_url = set_url_path(settings.base_url, 'api/v1/submit')
+ end
+ if not settings.actions_url then
+ settings.actions_url = set_url_path(settings.base_url, 'api/v1/actions/')
+ end
+end
+
+redis_params = lua_redis.parse_redis_server(N)
+if redis_params then
+ cache_context = redis_cache.create_cache_context(redis_params, {
+ cache_prefix = settings.cache_prefix,
+ cache_ttl = settings.cache_ttl,
+ cache_format = 'json',
+ cache_use_hashing = false
+ })
+end
+
+local submission_id = rspamd_config:register_symbol({
+ name = settings.submission_symbol,
+ type = 'normal',
+ group = N,
+ callback = submit_cb
+})
+
+local top_options = rspamd_config:get_all_opt('options')
+if settings.request_ttl and settings.request_ttl >= (top_options.task_timeout * 0.8) then
+ rspamd_logger.warn(rspamd_config, [[request ttl is >= 80% of task timeout, won't wait on processing]])
+ use_request_ttl = false
+elseif not settings.request_ttl then
+ use_request_ttl = false
+end
+
+local parent_id
+if use_request_ttl then
+ parent_id = submission_id
+else
+ parent_id = rspamd_config:register_symbol({
+ name = settings.action_symbol_prefix,
+ type = 'postfilter',
+ priority = lua_util.symbols_priorities.high - 1,
+ group = N,
+ callback = action_cb
+ })
+end
+
+for k in pairs(contextal_actions) do
+ rspamd_config:register_symbol({
+ name = settings.action_symbol_prefix .. '_' .. k,
+ parent = parent_id,
+ type = 'virtual',
+ group = N,
+ })
+end
diff --git a/src/plugins/lua/greylist.lua b/src/plugins/lua/greylist.lua
index e4a633233..934e17bce 100644
--- a/src/plugins/lua/greylist.lua
+++ b/src/plugins/lua/greylist.lua
@@ -122,6 +122,29 @@ local function data_key(task)
local h = hash.create()
h:update(body, len)
+ local subject = task:get_subject() or ''
+ h:update(subject)
+
+ -- Take recipients into account
+ local rcpt = task:get_recipients('smtp')
+ if rcpt then
+ table.sort(rcpt, function(r1, r2)
+ return r1['addr'] < r2['addr']
+ end)
+
+ fun.each(function(r)
+ h:update(r['addr'])
+ end, rcpt)
+ end
+
+ -- Use from as well, but mime one
+ local from = task:get_from('mime')
+
+ local addr = '<>'
+ if from and from[1] then
+ addr = from[1]['addr']
+ end
+ h:update(addr)
local b32 = settings['key_prefix'] .. 'b' .. h:base32():sub(1, 20)
task:get_mempool():set_variable("grey_bodyhash", b32)
diff --git a/src/plugins/lua/hfilter.lua b/src/plugins/lua/hfilter.lua
index 6bc011b83..a783565ab 100644
--- a/src/plugins/lua/hfilter.lua
+++ b/src/plugins/lua/hfilter.lua
@@ -199,9 +199,10 @@ local function check_regexp(str, regexp_text)
return re:match(str)
end
-local function add_static_map(data)
+local function add_static_map(data, description)
return rspamd_config:add_map {
type = 'regexp_multi',
+ description = description,
url = {
upstreams = 'static',
data = data,
@@ -568,16 +569,16 @@ local function append_t(t, a)
end
end
if config['helo_enabled'] then
- checks_hello_bareip_map = add_static_map(checks_hello_bareip)
- checks_hello_badip_map = add_static_map(checks_hello_badip)
- checks_hellohost_map = add_static_map(checks_hellohost)
- checks_hello_map = add_static_map(checks_hello)
+ checks_hello_bareip_map = add_static_map(checks_hello_bareip, 'Hfilter: HELO bare ip')
+ checks_hello_badip_map = add_static_map(checks_hello_badip, 'Hfilter: HELO bad ip')
+ checks_hellohost_map = add_static_map(checks_hellohost, 'Hfilter: HELO host')
+ checks_hello_map = add_static_map(checks_hello, 'Hfilter: HELO')
append_t(symbols_enabled, symbols_helo)
timeout = math.max(timeout, rspamd_config:get_dns_timeout() * 3)
end
if config['hostname_enabled'] then
if not checks_hellohost_map then
- checks_hellohost_map = add_static_map(checks_hellohost)
+ checks_hellohost_map = add_static_map(checks_hellohost, 'Hfilter: HOSTNAME')
end
append_t(symbols_enabled, symbols_hostname)
timeout = math.max(timeout, rspamd_config:get_dns_timeout())
diff --git a/src/rspamadm/lua_repl.c b/src/rspamadm/lua_repl.c
index 1d6da5aa9..9ad790d20 100644
--- a/src/rspamadm/lua_repl.c
+++ b/src/rspamadm/lua_repl.c
@@ -1,5 +1,5 @@
/*
- * Copyright 2024 Vsevolod Stakhov
+ * Copyright 2025 Vsevolod Stakhov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -232,7 +232,6 @@ rspamadm_exec_input(lua_State *L, const char *input)
int i, cbref;
int top = 0;
char outbuf[8192];
- struct lua_logger_trace tr;
struct thread_entry *thread = lua_thread_pool_get_for_config(rspamd_main->cfg);
L = thread->lua_state;
@@ -272,8 +271,7 @@ rspamadm_exec_input(lua_State *L, const char *input)
rspamd_printf("local function: %d\n", cbref);
}
else {
- memset(&tr, 0, sizeof(tr));
- lua_logger_out_type(L, i, outbuf, sizeof(outbuf) - 1, &tr,
+ lua_logger_out(L, i, outbuf, sizeof(outbuf),
LUA_ESCAPE_UNPRINTABLE);
rspamd_printf("%s\n", outbuf);
}
@@ -393,7 +391,6 @@ rspamadm_lua_message_handler(lua_State *L, int argc, char **argv)
gpointer map;
gsize len;
char outbuf[8192];
- struct lua_logger_trace tr;
if (argv[1] == NULL) {
rspamd_printf("no callback is specified\n");
@@ -455,8 +452,7 @@ rspamadm_lua_message_handler(lua_State *L, int argc, char **argv)
rspamd_printf("lua callback for %s returned:\n", argv[i]);
for (j = old_top + 1; j <= lua_gettop(L); j++) {
- memset(&tr, 0, sizeof(tr));
- lua_logger_out_type(L, j, outbuf, sizeof(outbuf), &tr,
+ lua_logger_out(L, j, outbuf, sizeof(outbuf),
LUA_ESCAPE_UNPRINTABLE);
rspamd_printf("%s\n", outbuf);
}
@@ -705,8 +701,8 @@ rspamadm_lua_run_repl(lua_State *L, bool is_batch)
g_string_append(tb, " \n");
}
}
- }
#endif
+ }
}
}