aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrew Lewis <nerf@judo.za.org>2014-01-01 18:00:55 +0200
committerAndrew Lewis <nerf@judo.za.org>2014-01-01 18:00:55 +0200
commitb9a65473e352d23485377ca9f895e2716cd624fe (patch)
tree35dc4e2973153eb00b5979098de17631b7d6495b
parent932f68cf1607335a4cd820b781466b8545b1a084 (diff)
parent3d97b8ca01fea56ba9371cb3992a9cb2c791030e (diff)
downloadrspamd-b9a65473e352d23485377ca9f895e2716cd624fe.tar.gz
rspamd-b9a65473e352d23485377ca9f895e2716cd624fe.zip
Merge remote-tracking branch 'upstream/master'
-rw-r--r--.hgtags9
-rw-r--r--CMakeLists.txt304
-rw-r--r--ChangeLog59
-rw-r--r--centos/rspamd.spec118
-rw-r--r--centos/sources/rspamd.init13
-rw-r--r--conf/2tld.inc6260
-rw-r--r--conf/lua/hfilter.lua310
-rw-r--r--conf/lua/rspamd.lua5
-rw-r--r--conf/metrics.conf106
-rw-r--r--conf/modules.conf94
-rw-r--r--conf/options.conf1
-rw-r--r--config.h.in1
-rw-r--r--contrib/lgpl/CMakeLists.txt4
-rw-r--r--debian/changelog30
-rw-r--r--debian/control6
-rw-r--r--debian/postrm22
-rw-r--r--debian/preinst19
-rwxr-xr-xdebian/rules7
-rw-r--r--debian/source.lintian-overrides3
-rw-r--r--doc/Makefile12
-rwxr-xr-xdoc/makeman.sh17
-rw-r--r--doc/markdown/architecture/index.md117
-rw-r--r--doc/markdown/configuration/index.md0
-rw-r--r--doc/markdown/index.md22
-rw-r--r--doc/markdown/lua/index.md0
-rw-r--r--doc/markdown/modules/chartable.md0
-rw-r--r--doc/markdown/modules/dkim.md0
-rw-r--r--doc/markdown/modules/emails.md0
-rw-r--r--doc/markdown/modules/forged_recipients.md0
-rw-r--r--doc/markdown/modules/fuzzy_check.md0
-rw-r--r--doc/markdown/modules/index.md64
-rw-r--r--doc/markdown/modules/maillist.md0
-rw-r--r--doc/markdown/modules/multimap.md0
-rw-r--r--doc/markdown/modules/once_received.md0
-rw-r--r--doc/markdown/modules/phishing.md0
-rw-r--r--doc/markdown/modules/ratelimit.md0
-rw-r--r--doc/markdown/modules/rbl.md0
-rw-r--r--doc/markdown/modules/regexp.md0
-rw-r--r--doc/markdown/modules/spf.md0
-rw-r--r--doc/markdown/modules/surbl.md0
-rw-r--r--doc/markdown/modules/trie.md0
-rw-r--r--doc/markdown/workers/index.md0
-rw-r--r--doc/rspamc.1438
-rw-r--r--doc/rspamc.1.md134
-rw-r--r--doc/rspamc.pod138
-rw-r--r--doc/rspamd.8316
-rw-r--r--doc/rspamd.8.md90
-rw-r--r--doc/rspamd.pod79
-rw-r--r--lib/CMakeLists.txt20
-rw-r--r--perl/MANIFEST3
-rw-r--r--perl/Makefile.PL.in11
-rw-r--r--perl/lib/Mail/Rspamd/Client.pm1390
-rw-r--r--perl/lib/Mail/Rspamd/Config.pm593
-rw-r--r--src/buffer.c6
-rw-r--r--src/cfg_file.h10
-rw-r--r--src/cfg_rcl.c11
-rw-r--r--src/cfg_rcl.h9
-rw-r--r--src/cfg_utils.c48
-rw-r--r--src/chacha_private.h228
-rw-r--r--src/client/CMakeLists.txt11
-rw-r--r--src/client/rspamc.c120
-rw-r--r--src/controller.c127
-rw-r--r--src/dkim.c38
-rw-r--r--src/dns.c622
-rw-r--r--src/dns.h68
-rw-r--r--src/filter.c12
-rw-r--r--src/fuzzy_storage.c96
-rw-r--r--src/logger.c50
-rw-r--r--src/logger.h2
-rw-r--r--src/lua/lua_config.c55
-rw-r--r--src/lua/lua_dns.c35
-rw-r--r--src/main.c48
-rw-r--r--src/main.h13
-rw-r--r--src/plugins/fuzzy_check.c802
-rw-r--r--src/plugins/lua/emails.lua119
-rw-r--r--src/plugins/lua/forged_recipients.lua2
-rw-r--r--src/plugins/lua/multimap.lua2
-rw-r--r--src/plugins/lua/once_received.lua52
-rw-r--r--src/plugins/lua/rbl.lua4
-rw-r--r--src/plugins/lua/whitelist.lua12
-rw-r--r--src/printf.c385
-rw-r--r--src/printf.h29
-rw-r--r--src/spf.c2
-rw-r--r--src/statfile.c10
-rw-r--r--src/symbols_cache.c45
-rw-r--r--src/symbols_cache.h1
-rw-r--r--src/ucl/include/ucl.h33
-rw-r--r--src/ucl/src/ucl_emitter.c49
-rw-r--r--src/ucl/src/ucl_hash.c3
-rw-r--r--src/ucl/src/ucl_internal.h13
-rw-r--r--src/ucl/src/ucl_parser.c317
-rw-r--r--src/ucl/src/ucl_util.c126
-rw-r--r--src/ucl/src/xxhash.c2
-rw-r--r--src/ucl/tests/rcl_test.json.xzbin1853872 -> 0 bytes
-rw-r--r--src/url.c17
-rw-r--r--src/util.c102
-rw-r--r--src/util.h30
-rw-r--r--src/worker.c14
-rw-r--r--src/worker_util.c2
99 files changed, 10099 insertions, 4498 deletions
diff --git a/.hgtags b/.hgtags
index 38676121a..e066f39e1 100644
--- a/.hgtags
+++ b/.hgtags
@@ -45,3 +45,12 @@ bf9307c5d78bd9b06e9fcae008db959aad1e62d2 0.5.4
34ec644af33ca2040b6e24f5ceadd311b502618e 0.5.6
e11d0fa6d58038a59ed5ed5cba04ec2b1089d61b 0.6.0
fd359d2ef59b1c0e35ca6c1db7f0b4cb5cf7e9fd 0.6.1
+e34a90b92b248cca1b8cb03052084e5e441ec21d 0.6.2
+28685d33512977c42fdfb3000ee05a28f6097794 0.6.3
+7efe36ce9272d6c6c521041ec8c587ee79f7ba7e 0.6.4
+7efe36ce9272d6c6c521041ec8c587ee79f7ba7e 0.6.4
+0000000000000000000000000000000000000000 0.6.4
+0000000000000000000000000000000000000000 0.6.4
+93e5d880b3898e8531f5968c295fca4bec845d68 0.6.4
+d7ffd19265d09c7b5b13409a730a3a987ea36f26 0.6.5
+3d18c18889ffd933cbfde99ede231f8869c3b8d8 0.6.6
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8937fe937..ffbc24974 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -10,10 +10,10 @@ PROJECT(rspamd C)
SET(RSPAMD_VERSION_MAJOR 0)
SET(RSPAMD_VERSION_MINOR 6)
-SET(RSPAMD_VERSION_PATCH 1)
+SET(RSPAMD_VERSION_PATCH 6)
SET(RSPAMD_VERSION "${RSPAMD_VERSION_MAJOR}.${RSPAMD_VERSION_MINOR}.${RSPAMD_VERSION_PATCH}")
-SET(RSPAMD_MASTER_SITE_URL "http://bitbucket.org/vstakhov/rspamd")
+SET(RSPAMD_MASTER_SITE_URL "https://rspamd.com")
IF(NOT RSPAMD_USER)
SET(RSPAMD_USER "nobody")
@@ -28,7 +28,6 @@ OPTION(DEBUG_MODE "Enable debug output [default: ON]"
OPTION(ENABLE_OPTIMIZATION "Enable optimization [default: OFF]" OFF)
OPTION(SKIP_RELINK_RPATH "Skip relinking and full RPATH for the install tree" OFF)
OPTION(ENABLE_REDIRECTOR "Enable redirector install [default: OFF]" OFF)
-OPTION(ENABLE_PROFILING "Enable profiling [default: OFF]" OFF)
OPTION(ENABLE_GPERF_TOOLS "Enable google perftools [default: OFF]" OFF)
OPTION(ENABLE_STATIC "Enable static compiling [default: OFF]" OFF)
OPTION(ENABLE_LUAJIT "Link with libluajit [default: OFF]" OFF)
@@ -36,8 +35,10 @@ OPTION(ENABLE_JUDY "Find and link with Judy library [default: 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: OFF]" OFF)
+OPTION(ENABLE_URL_INCLUDE "Enable urls in ucl includes (requires libcurl or libfetch) [default: OFF]" OFF)
OPTION(NO_SHARED "Build internal libs static [default: OFF]" OFF)
OPTION(FORCE_GMIME24 "Link with gmime2.4 [default: OFF]" OFF)
+OPTION(INSTALL_EXAMPLES "Install examples [default: OFF]" OFF)
# Build optimized code for following CPU (default i386)
#SET(CPU_TUNE "i686")
@@ -159,6 +160,7 @@ MACRO(AddModules MLIST WLIST)
#ENDIF(NOT EXISTS "src/modules.c")
ENDMACRO(AddModules MLIST WLIST)
+# Find lua installation
MACRO(FindLua _major _minor)
# Find lua libraries
MESSAGE(STATUS "Check for lua ${_major}.${_minor}")
@@ -260,6 +262,33 @@ FUNCTION(INSTALL_IF_NOT_EXISTS src dest suffix)
")
ENDFUNCTION(INSTALL_IF_NOT_EXISTS)
+# Process required package by using FindPackage and calling for INCLUDE_DIRECTORIES and
+# setting list of required libraries
+MACRO(ProcessPackage var _name0)
+ PKG_SEARCH_MODULE(${var} REQUIRED "${_name0}" ${ARGN})
+ IF(${var}_FOUND)
+ SET(WITH_${var} 1)
+ IF(ENABLE_STATIC MATCHES "ON")
+ SET(_XPREFIX "${var}_STATIC")
+ ELSE(ENABLE_STATIC MATCHES "ON")
+ SET(_XPREFIX "${var}")
+ ENDIF(ENABLE_STATIC MATCHES "ON")
+ FOREACH(_arg ${${_XPREFIX}_INCLUDE_DIRS})
+ INCLUDE_DIRECTORIES("${_arg}")
+ ENDFOREACH(_arg ${${_XPREFIX}_INCLUDE_DIRS})
+ FOREACH(_arg ${${_XPREFIX}_LIBRARY_DIRS})
+ LINK_DIRECTORIES("${_arg}")
+ ENDFOREACH(_arg ${${_XPREFIX}_LIBRARY_DIRS})
+ # Handle other CFLAGS and LDFLAGS
+ FOREACH(_arg ${${_XPREFIX}_CFLAGS_OTHER})
+ SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${_arg}")
+ ENDFOREACH(_arg ${${_XPREFIX}_CFLAGS_OTHER})
+ FOREACH(_arg ${${_XPREFIX}_LDFLAGS_OTHER})
+ SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${_arg}")
+ ENDFOREACH(_arg ${${_XPREFIX}_LDFLAGS_OTHER})
+ LIST(APPEND RSPAMD_REQUIRED_LIBRARIES "${${_XPREFIX}_LIBRARIES}")
+ ENDIF(${var}_FOUND)
+ENDMACRO(ProcessPackage name)
############################# CONFIG SECTION #############################################
# Initial set
@@ -296,20 +325,21 @@ IF(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
/usr/lib
/usr/local/lib
DOC "Path where the libintl library can be found")
- LIST(APPEND CMAKE_REQUIRED_LIBRARIES ${LIBINTL_LIBRARY})
+ LIST(APPEND RSPAMD_REQUIRED_LIBRARIES ${LIBINTL_LIBRARY})
MESSAGE(STATUS "Configuring for FreeBSD")
ENDIF(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
IF(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_BSD_SOURCE -DDARWIN")
+ SET(CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS "${CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS} -undefined dynamic_lookup")
FIND_LIBRARY(LIBINTL_LIBRARY NAMES intl PATHS /lib
/opt/lib
/usr/lib
/usr/local/lib
DOC "Path where the libintl library can be found")
- LIST(APPEND CMAKE_REQUIRED_LIBRARIES ${LIBINTL_LIBRARY})
+ LIST(APPEND RSPAMD_REQUIRED_LIBRARIES ${LIBINTL_LIBRARY})
MESSAGE(STATUS "Configuring for Darwin")
ENDIF(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
@@ -327,6 +357,10 @@ IF(CMAKE_SYSTEM_NAME STREQUAL "Linux")
LIST(APPEND CMAKE_REQUIRED_LIBRARIES rt)
LIST(APPEND CMAKE_REQUIRED_LIBRARIES dl)
+ #XXX: gio bug workaround
+ IF(ENABLE_STATIC MATCHES "ON")
+ LIST(APPEND CMAKE_REQUIRED_LIBRARIES selinux)
+ ENDIF(ENABLE_STATIC MATCHES "ON")
MESSAGE(STATUS "Configuring for Linux")
IF(EXISTS "/etc/debian_version")
SET(LINUX_START_SCRIPT "rspamd_debian.in")
@@ -396,27 +430,9 @@ ELSE(NOT LUA_FOUND)
INCLUDE_DIRECTORIES("${LUA_INCLUDE_DIR}")
ENDIF(NOT LUA_FOUND)
-# Check and link to pcre
-pkg_check_modules(PCRE REQUIRED libpcre)
-IF(PCRE_INCLUDE_DIRS)
- INCLUDE_DIRECTORIES("${PCRE_INCLUDE_DIRS}")
-ENDIF(PCRE_INCLUDE_DIRS)
-IF(PCRE_LIBRARY_DIRS)
- LINK_DIRECTORIES("${PCRE_LIBRARY_DIRS}")
-ENDIF(PCRE_LIBRARY_DIRS)
-
IF(ENABLE_SQLITE MATCHES "ON")
# Find optional sqlite3 support
- pkg_check_modules(SQLITE sqlite3>=3.6.0)
- IF(SQLITE_FOUND)
- SET(WITH_SQLITE 1)
- ENDIF(SQLITE_FOUND)
- IF(SQLITE_INCLUDE_DIRS)
- INCLUDE_DIRECTORIES("${SQLITE_INCLUDE_DIRS}")
- ENDIF(SQLITE_INCLUDE_DIRS)
- IF(SQLITE_LIBRARY_DIRS)
- LINK_DIRECTORIES("${SQLITE_LIBRARY_DIRS}")
- ENDIF(SQLITE_LIBRARY_DIRS)
+ ProcessPackage(SQLITE sqlite3>=3.6.0)
ENDIF(ENABLE_SQLITE MATCHES "ON")
#Check for openssl (required for dkim)
@@ -425,129 +441,19 @@ IF(OPENSSL_FOUND)
INCLUDE_DIRECTORIES("${OPENSSL_INCLUDE_DIR}")
ENDIF(OPENSSL_FOUND)
-IF(ENABLE_STATIC MATCHES "ON")
- pkg_check_modules(GLIB2 REQUIRED glib-2.0>=2.12 gthread-2.0 gmodule-2.0)
- SET(LINK_TYPE "STATIC")
-ELSE(ENABLE_STATIC MATCHES "ON")
- pkg_check_modules(GLIB2 REQUIRED glib-2.0>=2.12 gthread-2.0 gmodule-2.0)
- IF(NO_SHARED MATCHES "OFF")
- SET(LINK_TYPE "SHARED")
- ELSE(NO_SHARED MATCHES "OFF")
- SET(LINK_TYPE "STATIC")
- ENDIF(NO_SHARED MATCHES "OFF")
-ENDIF(ENABLE_STATIC MATCHES "ON")
-SET(GLIB2_VERSION "${GLIB2_glib-2.0_VERSION}")
-
-IF(GLIB2_VERSION VERSION_GREATER "2.30.0")
- pkg_check_modules(LIBFFI libffi)
- IF(LIBFFI_FOUND)
- SET(GLIB2_LDFLAGS "${GLIB2_LDFLAGS};${LIBFFI_LDFLAGS}")
- SET(GLIB2_LIBRARIES "${GLIB2_LIBRARIES};${LIBFFI_LIBRARIES}")
- SET(GLIB2_STATIC_LDFLAGS "${GLIB2_STATIC_LDFLAGS};${LIBFFI_STATIC_LDFLAGS}")
- SET(GLIB2_CFLAGS "${GLIB2_CFLAGS};${LIBFFI_CFLAGS}")
- ENDIF(LIBFFI_FOUND)
-ENDIF(GLIB2_VERSION VERSION_GREATER "2.30.0")
-pkg_check_modules(GMIME2 gmime-2.0)
+ProcessPackage(GLIB2 glib-2.0>=2.12)
+ProcessPackage(GTHREAD gthread-2.0)
IF(ENABLE_HIREDIS MATCHES "ON")
# Try to find hiredis library
- pkg_check_modules(HIREDIS REQUIRED libhiredis)
- IF(HIREDIS_INCLUDE_DIRS)
- INCLUDE_DIRECTORIES("${HIREDIS_INCLUDE_DIRS}")
- ENDIF(HIREDIS_INCLUDE_DIRS)
- IF(HIREDIS_LIBRARY_DIRS)
- LINK_DIRECTORIES("${HIREDIS_LIBRARY_DIRS}")
- ENDIF(HIREDIS_LIBRARY_DIRS)
- IF(HIREDIS_FOUND)
- SET(WITH_SYSTEM_HIREDIS 1)
- ENDIF(HIREDIS_FOUND)
+ ProcessPackage(HIREDIS libhiredis)
ENDIF(ENABLE_HIREDIS MATCHES "ON")
-# Try to link with gmime24
-IF(NOT GMIME2_FOUND OR FORCE_GMIME24)
- pkg_check_modules(GMIME24 gmime-2.4)
- IF(NOT GMIME24_FOUND)
- pkg_check_modules(GMIME24 REQUIRED gmime-2.6)
- ENDIF(NOT GMIME24_FOUND)
- SET(GMIME24 "yes")
- # Gmime2
- FOREACH(arg ${GMIME24_CFLAGS})
- SET(GMIME_CFLAGS "${GMIME_CFLAGS} ${arg}")
- ENDFOREACH(arg ${GMIME24_CFLAGS})
-
- IF(ENABLE_STATIC MATCHES "ON")
- FOREACH(arg ${GMIME24_STATIC_LDFLAGS})
- SET(GMIME_LDFLAGS "${GMIME_LDFLAGS} ${arg}")
- ENDFOREACH(arg ${GMIME24_LDFLAGS})
- ELSE(ENABLE_STATIC MATCHES "ON")
- FOREACH(arg ${GMIME24_LDFLAGS})
- SET(GMIME_LDFLAGS "${GMIME_LDFLAGS} ${arg}")
- ENDFOREACH(arg ${GMIME24_LDFLAGS})
- ENDIF(ENABLE_STATIC MATCHES "ON")
- IF(GMIME24_INCLUDE_DIRS)
- INCLUDE_DIRECTORIES(${GMIME24_INCLUDE_DIRS})
- ENDIF(GMIME24_INCLUDE_DIRS)
- IF(GMIME24_LIBRARY_DIRS)
- LINK_DIRECTORIES(${GMIME24_LIBRARY_DIRS})
- ENDIF(GMIME24_LIBRARY_DIRS)
-ELSE(NOT GMIME2_FOUND OR FORCE_GMIME24)
- # Gmime2
- FOREACH(arg ${GMIME2_CFLAGS})
- SET(GMIME_CFLAGS "${GMIME_CFLAGS} ${arg}")
- ENDFOREACH(arg ${GMIME2_CFLAGS})
-
- IF(ENABLE_STATIC MATCHES "ON")
- FOREACH(arg ${GMIME2_STATIC_LDFLAGS})
- SET(GMIME_LDFLAGS "${GMIME_LDFLAGS} ${arg}")
- ENDFOREACH(arg ${GMIME2_LDFLAGS})
- ELSE(ENABLE_STATIC MATCHES "ON")
- FOREACH(arg ${GMIME2_LDFLAGS})
- SET(GMIME_LDFLAGS "${GMIME_LDFLAGS} ${arg}")
- ENDFOREACH(arg ${GMIME2_LDFLAGS})
- ENDIF(ENABLE_STATIC MATCHES "ON")
- IF(GMIME2_INCLUDE_DIRS)
- INCLUDE_DIRECTORIES(${GMIME2_INCLUDE_DIRS})
- ENDIF(GMIME2_INCLUDE_DIRS)
- IF(GMIME2_LIBRARY_DIRS)
- LINK_DIRECTORIES(${GMIME2_LIBRARY_DIRS})
- ENDIF(GMIME2_LIBRARY_DIRS)
-ENDIF(NOT GMIME2_FOUND OR FORCE_GMIME24)
-
-# Make from ; separated list normal space separated list
-# Glib2
-FOREACH(arg ${GLIB2_CFLAGS})
- SET(GLIB_CFLAGS "${GLIB_CFLAGS} ${arg}")
-ENDFOREACH(arg ${GLIB2_CFLAGS})
-
-IF(ENABLE_STATIC MATCHES "ON")
- FOREACH(arg ${GLIB2_STATIC_LDFLAGS})
- SET(GLIB_LDFLAGS "${GLIB_LDFLAGS} ${arg}")
- ENDFOREACH(arg ${GLIB2_LDFLAGS})
-ELSE(ENABLE_STATIC MATCHES "ON")
- FOREACH(arg ${GLIB2_LDFLAGS})
- SET(GLIB_LDFLAGS "${GLIB_LDFLAGS} ${arg}")
- ENDFOREACH(arg ${GLIB2_LDFLAGS})
-ENDIF(ENABLE_STATIC MATCHES "ON")
+ProcessPackage(GMIME2 gmime-2.6 gmime-2.4 gmime-2.0)
+IF(GMIME2_VERSION VERSION_GREATER "2.4.0")
+ SET(GMIME24 1)
+ENDIF(GMIME2_VERSION VERSION_GREATER "2.4.0")
-
-INCLUDE_DIRECTORIES(${GLIB2_INCLUDE_DIRS})
-LINK_DIRECTORIES(${GLIB2_LIBRARY_DIRS})
-
-
-FIND_LIBRARY(LIBZ_LIBRARY NAMES z PATH_SUFFIXES lib64 lib
- PATHS
- ~/Library/Frameworks
- /Library/Frameworks
- /usr/local
- /usr
- /sw
- /opt/local
- /opt/csw
- /opt
- DOC "Path where the libz library can be found")
-IF(NOT LIBZ_LIBRARY)
- MESSAGE(FATAL_ERROR "libz is required for libgmime")
-ENDIF(NOT LIBZ_LIBRARY)
# Check for libevent
FIND_LIBRARY(LIBEVENT_LIBRARY NAMES event PATH_SUFFIXES lib64 lib
@@ -631,20 +537,6 @@ IF(ENABLE_DB MATCHES "ON")
ENDIF(LIBDB_LIBRARY)
ENDIF(ENABLE_DB MATCHES "ON")
-IF(ENABLE_PROFILING MATCHES "ON")
- SET(WITH_PROFILER 1)
- SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pg")
- SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pg")
-ENDIF(ENABLE_PROFILING MATCHES "ON")
-
-# Static build
-
-IF(ENABLE_STATIC MATCHES "ON")
- SET(BUILD_STATIC 1)
- SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static -pthread")
- MESSAGE(STATUS "Static build of rspamd, no custom plugins support")
-ENDIF(ENABLE_STATIC MATCHES "ON")
-
# Google performance tools
IF(ENABLE_GPERF_TOOLS MATCHES "ON")
@@ -692,7 +584,7 @@ IF(LIBUTIL_LIBRARY)
/usr/local/include
DOC "Path to libutil header")
IF(HAVE_LIBUTIL_H)
- LIST(APPEND CMAKE_REQUIRED_LIBRARIES util)
+ LIST(APPEND RSPAMD_REQUIRED_LIBRARIES util)
CHECK_FUNCTION_EXISTS(pidfile_open HAVE_PIDFILE)
CHECK_FUNCTION_EXISTS(pidfile_fileno HAVE_PIDFILE_FILENO)
ENDIF(HAVE_LIBUTIL_H)
@@ -700,31 +592,49 @@ ENDIF(LIBUTIL_LIBRARY)
# Find libfetch (for FreeBSD)
-FIND_LIBRARY(LIBFETCH_LIBRARY NAMES fetch PATHS PATH_SUFFIXES lib64 lib
- PATHS
- ~/Library/Frameworks
- /Library/Frameworks
- /usr/local
- /usr
- /sw
- /opt/local
- /opt/csw
- /opt
- DOC "Path where the libfetch library can be found")
-IF(LIBFETCH_LIBRARY)
- FIND_FILE(HAVE_FETCH_H NAMES fetch.h PATHS /usr/include
- /opt/include
- /usr/local/include
- DOC "Path to libfetch header")
-ELSE(LIBFETCH_LIBRARY)
- # Try to find libcurl
- INCLUDE(FindCURL)
- IF(NOT CURL_FOUND)
- MESSAGE(WARNING "Neither libcurl nor libfetch were found, no support of URL includes in configuration")
- ELSE(NOT CURL_FOUND)
- INCLUDE_DIRECTORIES("${CURL_INCLUDE_DIRS}")
- ENDIF(NOT CURL_FOUND)
-ENDIF(LIBFETCH_LIBRARY)
+IF(ENABLE_URL_INCLUDE MATCHES "ON")
+ FIND_LIBRARY(LIBFETCH_LIBRARY NAMES fetch PATHS PATH_SUFFIXES lib64 lib
+ PATHS
+ ~/Library/Frameworks
+ /Library/Frameworks
+ /usr/local
+ /usr
+ /sw
+ /opt/local
+ /opt/csw
+ /opt
+ DOC "Path where the libfetch library can be found")
+ IF(LIBFETCH_LIBRARY)
+ FIND_FILE(HAVE_FETCH_H NAMES fetch.h PATHS /usr/include
+ /opt/include
+ /usr/local/include
+ DOC "Path to libfetch header")
+ ELSE(LIBFETCH_LIBRARY)
+ # Try to find libcurl
+ ProcessPackage(CURL libcurl)
+ IF(NOT CURL_FOUND)
+ MESSAGE(WARNING "Neither libcurl nor libfetch were found, no support of URL includes in configuration")
+ ENDIF(NOT CURL_FOUND)
+ ENDIF(LIBFETCH_LIBRARY)
+ENDIF(ENABLE_URL_INCLUDE MATCHES "ON")
+
+# Static build
+
+IF(ENABLE_STATIC MATCHES "ON")
+ SET(BUILD_STATIC 1)
+ SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static -pthread")
+ MESSAGE(STATUS "Static build of rspamd, no custom plugins support")
+ SET(LINK_TYPE "STATIC")
+ SET(NO_SHARED "ON")
+ELSE(ENABLE_STATIC MATCHES "ON")
+ IF(NO_SHARED MATCHES "OFF")
+ SET(LINK_TYPE "SHARED")
+ ELSE(NO_SHARED MATCHES "OFF")
+ SET(LINK_TYPE "STATIC")
+ ENDIF(NO_SHARED MATCHES "OFF")
+ENDIF(ENABLE_STATIC MATCHES "ON")
+
+LIST(APPEND RSPAMD_REQUIRED_LIBRARIES "${CMAKE_REQUIRED_LIBRARIES}")
# Process with warn flags
SET(CMAKE_C_WARN_FLAGS "")
@@ -862,6 +772,7 @@ CHECK_FUNCTION_EXISTS(wait4 HAVE_WAIT4)
CHECK_FUNCTION_EXISTS(waitpid HAVE_WAITPID)
CHECK_FUNCTION_EXISTS(flock HAVE_FLOCK)
CHECK_FUNCTION_EXISTS(tanhl HAVE_TANHL)
+CHECK_FUNCTION_EXISTS(tanh HAVE_TANH)
CHECK_FUNCTION_EXISTS(expl HAVE_EXPL)
CHECK_FUNCTION_EXISTS(exp2l HAVE_EXP2L)
CHECK_FUNCTION_EXISTS(sendfile HAVE_SENDFILE)
@@ -924,9 +835,6 @@ IF(NOT HAVE_COMPATIBLE_QUEUE_H)
INCLUDE_DIRECTORIES(compat)
ENDIF(NOT HAVE_COMPATIBLE_QUEUE_H)
-
-SET(CONTRIBSRC "")
-
IF(NOT DESTDIR)
SET(DESTDIR $ENV{DESTDIR})
ENDIF(NOT DESTDIR)
@@ -1038,7 +946,7 @@ SET(CONFFILES
######################### LINK SECTION ###############################
-ADD_EXECUTABLE(rspamd ${RSPAMDSRC} ${CONTRIBSRC} ${PLUGINSSRC})
+ADD_EXECUTABLE(rspamd ${RSPAMDSRC} ${PLUGINSSRC})
SET_TARGET_PROPERTIES(rspamd PROPERTIES LINKER_LANGUAGE C)
SET_TARGET_PROPERTIES(rspamd PROPERTIES COMPILE_FLAGS "-DRSPAMD_MAIN")
IF(NOT DEBIAN_BUILD)
@@ -1061,33 +969,14 @@ ENDIF(HAVE_LIBEVENT2)
IF(WITH_DB)
TARGET_LINK_LIBRARIES(rspamd db)
ENDIF(WITH_DB)
-IF(SQLITE_LIBRARIES)
- TARGET_LINK_LIBRARIES(rspamd ${SQLITE_LIBRARIES})
-ENDIF(SQLITE_LIBRARIES)
IF(OPENSSL_FOUND)
TARGET_LINK_LIBRARIES(rspamd ${OPENSSL_LIBRARIES})
ENDIF(OPENSSL_FOUND)
-TARGET_LINK_LIBRARIES(rspamd ${PCRE_LIBRARIES})
-IF(GMIME24)
- TARGET_LINK_LIBRARIES(rspamd ${GMIME24_LIBRARIES})
-ELSE(GMIME24)
- TARGET_LINK_LIBRARIES(rspamd ${GMIME2_LIBRARIES})
-ENDIF(GMIME24)
-TARGET_LINK_LIBRARIES(rspamd ${GLIB2_LIBRARIES})
IF(HAVE_FETCH_H)
TARGET_LINK_LIBRARIES(rspamd fetch)
-ELSE(HAVE_FETCH_H)
- IF(CURL_FOUND)
- TARGET_LINK_LIBRARIES(rspamd ${CURL_LIBRARIES})
- ENDIF(CURL_FOUND)
ENDIF(HAVE_FETCH_H)
-TARGET_LINK_LIBRARIES(rspamd ${CMAKE_REQUIRED_LIBRARIES})
-
-IF(ENABLE_STATIC MATCHES "ON")
- TARGET_LINK_LIBRARIES(rspamd ${PCRE_LIBRARIES})
- TARGET_LINK_LIBRARIES(rspamd "z")
-ENDIF(ENABLE_STATIC MATCHES "ON")
+TARGET_LINK_LIBRARIES(rspamd ${RSPAMD_REQUIRED_LIBRARIES})
IF(ENABLE_LUAJIT MATCHES "ON")
TARGET_LINK_LIBRARIES(rspamd "${LUAJIT_LIBRARY}")
@@ -1124,6 +1013,9 @@ FOREACH(CONF_IDX RANGE ${CONFLIST_MAX})
ELSE(BUILD_PORT)
INSTALL_IF_NOT_EXISTS(${CONF_FILE} ${CONFDIR} "")
ENDIF(BUILD_PORT)
+ IF(INSTALL_EXAMPLES MATCHES "ON")
+ INSTALL(FILES ${CONF_FILE} DESTINATION ${EXAMPLESDIR})
+ ENDIF(INSTALL_EXAMPLES MATCHES "ON")
ENDFOREACH(CONF_IDX RANGE ${CONFLIST_MAX})
# Lua plugins
@@ -1148,4 +1040,4 @@ ENDFOREACH(LUA_CONF)
# Manual pages
INSTALL(FILES "doc/rspamd.8" DESTINATION ${MANDIR}/man8)
-INSTALL(FILES "doc/rspamc.1" DESTINATION ${MANDIR}/man1) \ No newline at end of file
+INSTALL(FILES "doc/rspamc.1" DESTINATION ${MANDIR}/man1)
diff --git a/ChangeLog b/ChangeLog
index 89add5be2..8501b9146 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,62 @@
+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
+
+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
+
+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
+
+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.
+
+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.
+
+0.6.1:
+ * 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
diff --git a/centos/rspamd.spec b/centos/rspamd.spec
index b7e6259b2..57ab3b071 100644
--- a/centos/rspamd.spec
+++ b/centos/rspamd.spec
@@ -7,32 +7,43 @@
%define USE_JUDY 0
+%if 0%{?suse_version}
+%define __cmake cmake
+%define __install install
+%define __make make
+%define __chown chown
+%endif
+
Name: rspamd
-Version: 0.6.1
+Version: 0.6.6
Release: 1
Summary: Rapid spam filtering system
Group: System Environment/Daemons
# BSD License (two clause)
# http://www.freebsd.org/copyright/freebsd-license.html
-License: BSD
-URL: https://bitbucket.org/vstakhov/rspamd/
+License: BSD2c
+URL: https://rspamd.com
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}
-%if USE_JUDY
-BuildRequires: cmake,glib2-devel,gmime-devel,libevent-devel,openssl-devel,lua-devel,Judy-devel
-Requires: glib2,gmime,lua,Judy,libevent
+%if "%{USE_JUDY}" == "1"
+BuildRequires: cmake,glib2-devel,gmime-devel,libevent-devel,openssl-devel,lua-devel,Judy-devel,pcre-devel
+Requires: lua, logrotate
%else
-BuildRequires: cmake,glib2-devel,gmime-devel,libevent-devel,openssl-devel,lua-devel
-Requires: glib2,gmime,lua,libevent
+BuildRequires: cmake,glib2-devel,gmime-devel,libevent-devel,openssl-devel,lua-devel,pcre-devel
+Requires: lua, logrotate
%endif
# for /user/sbin/useradd
+%if 0%{?suse_version}
+Requires(pre): shadow, %insserv_prereq, %fillup_prereq
+%else
Requires(pre): shadow-utils
Requires(post): chkconfig
# for /sbin/service
Requires(preun): chkconfig, initscripts
Requires(postun): initscripts
+%endif
-Source0: http://cdn.bitbucket.org/vstakhov/rspamd/downloads/%{name}-%{version}.tar.gz
+Source0: https://rspamd.com/downloads/%{name}-%{version}.tar.gz
Source1: %{name}.init
Source2: %{name}.logrotate
@@ -45,7 +56,6 @@ lua.
%setup -q
%build
-rm -rf %{buildroot}
%{__cmake} \
-DCMAKE_INSTALL_PREFIX=%{_prefix} \
-DCONFDIR=%{_sysconfdir}/rspamd \
@@ -61,7 +71,7 @@ rm -rf %{buildroot}
-DDEBIAN_BUILD=1 \
-DRSPAMD_GROUP=%{rspamd_group} \
-DRSPAMD_USER=%{rspamd_user} \
-%if USE_JUDY
+%if "%{USE_JUDY}" == "1"
-DENABLE_JUDY=ON
%else
-DENABLE_JUDY=OFF
@@ -73,15 +83,36 @@ rm -rf %{buildroot}
%{__make} install DESTDIR=%{buildroot} INSTALLDIRS=vendor
%{__install} -p -D -m 0755 %{SOURCE1} %{buildroot}%{_initrddir}/%{name}
+
+%if 0%{?suse_version}
+mkdir -p %{buildroot}%{_sbindir}
+ln -sf %{_initrddir}/rspamd %{buildroot}%{_sbindir}/rcrspamd
+%endif
+
%{__install} -p -D -m 0644 %{SOURCE2} %{buildroot}%{_sysconfdir}/logrotate.d/%{name}
%{__install} -d -p -m 0755 %{buildroot}%{rspamd_logdir}
-%{__install} -o %{rspamd_user} -g %{rspamd_group} -d -p -m 0755 %{buildroot}%{rspamd_home}
+%{__install} -d -p -m 0755 %{buildroot}%{rspamd_home}
%clean
rm -rf %{buildroot}
%pre
-%{_sbindir}/useradd -c "Rspamd user" -s /bin/false -r -d %{rspamd_home} %{rspamd_user} 2>/dev/null || :
+%{_sbindir}/groupadd -r %{rspamd_group} 2>/dev/null || :
+%{_sbindir}/useradd -g %{rspamd_group} -c "Rspamd user" -s /bin/false -r -d %{rspamd_home} %{rspamd_user} 2>/dev/null || :
+
+%if 0%{?suse_version}
+
+%post
+%fillup_and_insserv rspamd
+
+%preun
+%stop_on_removal rspamd
+
+%postun
+%restart_on_update rspamd
+%insserv_cleanup
+
+%else
%post
/sbin/chkconfig --add %{name}
@@ -97,6 +128,8 @@ if [ $1 -ge 1 ]; then
/sbin/service %{name} condrestart > /dev/null 2>&1 || :
fi
+%endif
+
%files
%defattr(-,root,root,-)
%{_initrddir}/%{name}
@@ -114,29 +147,52 @@ fi
%config(noreplace) %{rspamd_confdir}/workers.conf
%config(noreplace) %{_sysconfdir}/logrotate.d/%{name}
%dir %{rspamd_logdir}
+%dir %{rspamd_home}
+%dir %{rspamd_confdir}/lua/regexp
+%dir %{rspamd_confdir}/lua
%dir %{rspamd_confdir}
-%attr(755, %{rspamd_user}, %{rspamd_group}) %dir %{rspamd_home}
+%dir %{rspamd_pluginsdir}/lua
+%dir %{rspamd_pluginsdir}
%config(noreplace) %{rspamd_confdir}/2tld.inc
%config(noreplace) %{rspamd_confdir}/surbl-whitelist.inc
-%config(noreplace) %{rspamd_pluginsdir}/lua/forged_recipients.lua
-%config(noreplace) %{rspamd_pluginsdir}/lua/maillist.lua
-%config(noreplace) %{rspamd_pluginsdir}/lua/multimap.lua
-%config(noreplace) %{rspamd_pluginsdir}/lua/once_received.lua
-%config(noreplace) %{rspamd_pluginsdir}/lua/rbl.lua
-%config(noreplace) %{rspamd_pluginsdir}/lua/ratelimit.lua
-%config(noreplace) %{rspamd_pluginsdir}/lua/whitelist.lua
-%config(noreplace) %{rspamd_pluginsdir}/lua/phishing.lua
-%config(noreplace) %{rspamd_pluginsdir}/lua/trie.lua
-%config(noreplace) %{rspamd_pluginsdir}/lua/emails.lua
-%config(noreplace) %{rspamd_pluginsdir}/lua/ip_score.lua
-%config(noreplace) %{rspamd_confdir}/lua/regexp/drugs.lua
-%config(noreplace) %{rspamd_confdir}/lua/regexp/fraud.lua
-%config(noreplace) %{rspamd_confdir}/lua/regexp/headers.lua
-%config(noreplace) %{rspamd_confdir}/lua/regexp/lotto.lua
-%config(noreplace) %{rspamd_confdir}/lua/rspamd.lua
-%config(noreplace) %{rspamd_confdir}/lua/rspamd.classifiers.lua
+%{rspamd_pluginsdir}/lua/forged_recipients.lua
+%{rspamd_pluginsdir}/lua/maillist.lua
+%{rspamd_pluginsdir}/lua/multimap.lua
+%{rspamd_pluginsdir}/lua/once_received.lua
+%{rspamd_pluginsdir}/lua/rbl.lua
+%{rspamd_pluginsdir}/lua/ratelimit.lua
+%{rspamd_pluginsdir}/lua/whitelist.lua
+%{rspamd_pluginsdir}/lua/phishing.lua
+%{rspamd_pluginsdir}/lua/trie.lua
+%{rspamd_pluginsdir}/lua/emails.lua
+%{rspamd_pluginsdir}/lua/ip_score.lua
+%{rspamd_confdir}/lua/regexp/drugs.lua
+%{rspamd_confdir}/lua/regexp/fraud.lua
+%{rspamd_confdir}/lua/regexp/headers.lua
+%{rspamd_confdir}/lua/regexp/lotto.lua
+%{rspamd_confdir}/lua/rspamd.lua
+%{rspamd_confdir}/lua/hfilter.lua
+%{rspamd_confdir}/lua/rspamd.classifiers.lua
+%if 0%{?suse_version}
+%{_sbindir}/rcrspamd
+%endif
%changelog
+* Fri Dec 27 2013 Vsevolod Stakhov <vsevolod-at-highsecure.ru> 0.6.6-1
+- Update to 0.6.6.
+
+* Fri Dec 20 2013 Vsevolod Stakhov <vsevolod-at-highsecure.ru> 0.6.5-1
+- Update to 0.6.5.
+
+* Wed Dec 18 2013 Vsevolod Stakhov <vsevolod-at-highsecure.ru> 0.6.4-1
+- Update to 0.6.4.
+
+* Tue Dec 10 2013 Vsevolod Stakhov <vsevolod-at-highsecure.ru> 0.6.3-1
+- Update to 0.6.3.
+
+* Fri Dec 06 2013 Vsevolod Stakhov <vsevolod-at-highsecure.ru> 0.6.2-1
+- Update to 0.6.2.
+
* Tue Nov 19 2013 Vsevolod Stakhov <vsevolod-at-highsecure.ru> 0.6.0-1
- Update to 0.6.0.
diff --git a/centos/sources/rspamd.init b/centos/sources/rspamd.init
index 161bbc396..78714a935 100644
--- a/centos/sources/rspamd.init
+++ b/centos/sources/rspamd.init
@@ -1,11 +1,20 @@
#!/bin/sh
#
# rspamd - this script starts and stops the rspamd daemon
+### BEGIN INIT INFO
+# Provides: rspamd
+# Required-Start: $remote_fs $network
+# Required-Stop: $network $remote_fs
+# Default-Start: 5
+# Default-Stop:
+# Short-Description: Rspamd daemon
+# Description: Rspamd spam filtering daemon process
+### END INIT INFO
#
# chkconfig: - 85 15
# description: rspamd is a spam filtering system
# processname: rspamd
-# config: /etc/rspamd/rspamd.xml
+# config: /etc/rspamd/rspamd.conf
# config: /etc/sysconfig/rspamd
# pidfile: /var/run/rspamd/rspamd.pid
@@ -21,7 +30,7 @@
rspamd="/usr/bin/rspamd"
prog=$(basename $rspamd)
-RSPAMD_CONF_FILE="/etc/rspamd.xml"
+RSPAMD_CONF_FILE="/etc/rspamd/rspamd.conf"
RSPAMD_USER="rspamd"
RSPAMD_GROUP="rspamd"
diff --git a/conf/2tld.inc b/conf/2tld.inc
index 299f96229..f2b36f2fc 100644
--- a/conf/2tld.inc
+++ b/conf/2tld.inc
@@ -952,3 +952,6263 @@ blogindo.net
o-f.com
topfreewebhosting.com
freeadsensehost.com
+
+#ac
+com.ac
+edu.ac
+gov.ac
+net.ac
+mil.ac
+org.ac
+#ad
+nom.ad
+#ae
+co.ae
+net.ae
+org.ae
+sch.ae
+ac.ae
+gov.ae
+mil.ae
+#aero
+accident-investigation.aero
+accident-prevention.aero
+aerobatic.aero
+aeroclub.aero
+aerodrome.aero
+agents.aero
+aircraft.aero
+airline.aero
+airport.aero
+air-surveillance.aero
+airtraffic.aero
+air-traffic-control.aero
+ambulance.aero
+amusement.aero
+association.aero
+author.aero
+ballooning.aero
+broker.aero
+caa.aero
+cargo.aero
+catering.aero
+certification.aero
+championship.aero
+charter.aero
+civilaviation.aero
+club.aero
+conference.aero
+consultant.aero
+consulting.aero
+control.aero
+council.aero
+crew.aero
+design.aero
+dgca.aero
+educator.aero
+emergency.aero
+engine.aero
+engineer.aero
+entertainment.aero
+equipment.aero
+exchange.aero
+express.aero
+federation.aero
+flight.aero
+freight.aero
+fuel.aero
+gliding.aero
+government.aero
+groundhandling.aero
+group.aero
+hanggliding.aero
+homebuilt.aero
+insurance.aero
+journal.aero
+journalist.aero
+leasing.aero
+logistics.aero
+magazine.aero
+maintenance.aero
+marketplace.aero
+media.aero
+microlight.aero
+modelling.aero
+navigation.aero
+parachuting.aero
+paragliding.aero
+passenger-association.aero
+pilot.aero
+press.aero
+production.aero
+recreation.aero
+repbody.aero
+res.aero
+research.aero
+rotorcraft.aero
+safety.aero
+scientist.aero
+services.aero
+show.aero
+skydiving.aero
+software.aero
+student.aero
+taxi.aero
+trader.aero
+trading.aero
+trainer.aero
+union.aero
+workinggroup.aero
+works.aero
+#af
+gov.af
+com.af
+org.af
+net.af
+edu.af
+#ag
+com.ag
+org.ag
+net.ag
+co.ag
+nom.ag
+#ai
+off.ai
+com.ai
+net.ai
+org.ai
+#al
+com.al
+edu.al
+gov.al
+mil.al
+net.al
+org.al
+#am
+#an
+com.an
+net.an
+org.an
+edu.an
+#ao
+ed.ao
+gv.ao
+og.ao
+co.ao
+pb.ao
+it.ao
+#aq
+#ar
+com.ar
+gov.ar
+int.ar
+mil.ar
+net.ar
+org.ar
+#arpa
+e164.arpa
+in-addr.arpa
+ip6.arpa
+iris.arpa
+uri.arpa
+urn.arpa
+#as
+gov.as
+#asia
+#at
+ac.at
+co.at
+gv.at
+or.at
+#au
+com.au
+net.au
+org.au
+edu.au
+gov.au
+asn.au
+id.au
+csiro.au
+info.au
+conf.au
+oz.au
+act.au
+nsw.au
+nt.au
+qld.au
+sa.au
+tas.au
+vic.au
+wa.au
+act.edu.au
+nsw.edu.au
+nt.edu.au
+qld.edu.au
+sa.edu.au
+tas.edu.au
+vic.edu.au
+wa.edu.au
+act.gov.au
+nt.gov.au
+qld.gov.au
+sa.gov.au
+tas.gov.au
+vic.gov.au
+wa.gov.au
+#aw
+com.aw
+#ax
+#az
+com.az
+net.az
+int.az
+gov.az
+org.az
+edu.az
+info.az
+pp.az
+mil.az
+name.az
+pro.az
+biz.az
+#ba
+org.ba
+net.ba
+edu.ba
+gov.ba
+mil.ba
+unsa.ba
+unbi.ba
+co.ba
+com.ba
+rs.ba
+#bb
+biz.bb
+com.bb
+edu.bb
+gov.bb
+info.bb
+net.bb
+org.bb
+store.bb
+#bd
+com.bd
+edu.bd
+net.bd
+gov.bd
+org.bd
+mil.bd
+#be
+ac.be
+#bf
+gov.bf
+#bg
+a.bg
+b.bg
+c.bg
+d.bg
+e.bg
+f.bg
+g.bg
+h.bg
+i.bg
+j.bg
+k.bg
+l.bg
+m.bg
+n.bg
+o.bg
+p.bg
+q.bg
+r.bg
+s.bg
+t.bg
+u.bg
+v.bg
+w.bg
+x.bg
+y.bg
+z.bg
+0.bg
+1.bg
+2.bg
+3.bg
+4.bg
+5.bg
+6.bg
+7.bg
+8.bg
+9.bg
+#bh
+com.bh
+edu.bh
+net.bh
+org.bh
+gov.bh
+#bi
+co.bi
+com.bi
+edu.bi
+or.bi
+org.bi
+#biz
+#bj
+asso.bj
+barreau.bj
+gouv.bj
+#bm
+com.bm
+edu.bm
+gov.bm
+net.bm
+org.bm
+#bn
+com.bn
+edu.bn
+org.bn
+net.bn
+#bo
+com.bo
+edu.bo
+gov.bo
+gob.bo
+int.bo
+org.bo
+net.bo
+mil.bo
+tv.bo
+#br
+adm.br
+adv.br
+agr.br
+am.br
+arq.br
+art.br
+ato.br
+b.br
+bio.br
+blog.br
+bmd.br
+cim.br
+cng.br
+cnt.br
+com.br
+coop.br
+ecn.br
+eco.br
+edu.br
+emp.br
+eng.br
+esp.br
+etc.br
+eti.br
+far.br
+flog.br
+fm.br
+fnd.br
+fot.br
+fst.br
+g12.br
+ggf.br
+gov.br
+imb.br
+ind.br
+inf.br
+jor.br
+jus.br
+leg.br
+lel.br
+mat.br
+med.br
+mil.br
+mus.br
+net.br
+nom.br
+not.br
+ntr.br
+odo.br
+org.br
+ppg.br
+pro.br
+psc.br
+psi.br
+qsl.br
+radio.br
+rec.br
+slg.br
+srv.br
+taxi.br
+teo.br
+tmp.br
+trd.br
+tur.br
+tv.br
+vet.br
+vlog.br
+wiki.br
+zlg.br
+#bs
+com.bs
+net.bs
+org.bs
+edu.bs
+gov.bs
+#bt
+com.bt
+edu.bt
+gov.bt
+net.bt
+org.bt
+#bv
+#bw
+co.bw
+org.bw
+#by
+gov.by
+mil.by
+com.by
+of.by
+#bz
+com.bz
+net.bz
+org.bz
+edu.bz
+gov.bz
+#ca
+ab.ca
+bc.ca
+mb.ca
+nb.ca
+nf.ca
+nl.ca
+ns.ca
+nt.ca
+nu.ca
+on.ca
+pe.ca
+qc.ca
+sk.ca
+yk.ca
+gc.ca
+#cat
+#cc
+#cd
+gov.cd
+#cf
+#cg
+#ch
+#ci
+org.ci
+or.ci
+com.ci
+co.ci
+edu.ci
+ed.ci
+ac.ci
+net.ci
+go.ci
+asso.ci
+aeroport.ci
+int.ci
+presse.ci
+md.ci
+gouv.ci
+#ck
+.co.ck
+.org.ck
+.edu.ck
+.gov.ck
+.net.ck
+.gen.ck
+.biz.ck
+.info.ck
+#cl
+gov.cl
+gob.cl
+co.cl
+mil.cl
+#cm
+gov.cm
+#cn
+ac.cn
+com.cn
+edu.cn
+gov.cn
+net.cn
+org.cn
+mil.cn
+ah.cn
+bj.cn
+cq.cn
+fj.cn
+gd.cn
+gs.cn
+gz.cn
+gx.cn
+ha.cn
+hb.cn
+he.cn
+hi.cn
+hl.cn
+hn.cn
+jl.cn
+js.cn
+jx.cn
+ln.cn
+nm.cn
+nx.cn
+qh.cn
+sc.cn
+sd.cn
+sh.cn
+sn.cn
+sx.cn
+tj.cn
+xj.cn
+xz.cn
+yn.cn
+zj.cn
+hk.cn
+mo.cn
+tw.cn
+#co
+arts.co
+com.co
+edu.co
+firm.co
+gov.co
+info.co
+int.co
+mil.co
+net.co
+nom.co
+org.co
+rec.co
+web.co
+#com
+#coop
+#cr
+ac.cr
+co.cr
+ed.cr
+fi.cr
+go.cr
+or.cr
+sa.cr
+#cu
+com.cu
+edu.cu
+org.cu
+net.cu
+gov.cu
+inf.cu
+#cv
+#cw
+com.cw
+edu.cw
+net.cw
+org.cw
+#cx
+gov.cx
+#cy
+com.cy
+biz.cy
+info.cy
+ltd.cy
+pro.cy
+net.cy
+org.cy
+name.cy
+tm.cy
+ac.cy
+ekloges.cy
+press.cy
+parliament.cy
+#cz
+#de
+#dj
+#dk
+#dm
+com.dm
+net.dm
+org.dm
+edu.dm
+gov.dm
+#do
+art.do
+com.do
+edu.do
+gob.do
+gov.do
+mil.do
+net.do
+org.do
+sld.do
+web.do
+#dz
+com.dz
+org.dz
+net.dz
+gov.dz
+edu.dz
+asso.dz
+pol.dz
+art.dz
+#ec
+com.ec
+info.ec
+net.ec
+fin.ec
+k12.ec
+med.ec
+pro.ec
+org.ec
+edu.ec
+gov.ec
+gob.ec
+mil.ec
+#edu
+#ee
+edu.ee
+gov.ee
+riik.ee
+lib.ee
+med.ee
+com.ee
+pri.ee
+aip.ee
+org.ee
+fie.ee
+eg
+com.eg
+edu.eg
+eun.eg
+gov.eg
+mil.eg
+name.eg
+net.eg
+org.eg
+sci.eg
+#er
+com.er
+edu.er
+gov.er
+mil.er
+net.er
+org.er
+ind.er
+#es
+com.es
+nom.es
+org.es
+gob.es
+edu.es
+#et
+com.et
+gov.et
+org.et
+edu.et
+net.et
+biz.et
+name.et
+info.et
+#eu
+#fi
+aland.fi
+#fj
+ac.fj
+biz.fj
+com.fj
+info.fj
+mil.fj
+name.fj
+net.fj
+org.fj
+pro.fj
+#fk
+co.fk
+org.fk
+gov.fk
+ac.fk
+nom.fk
+net.fk
+#fm
+#fo
+#fr
+com.fr
+asso.fr
+nom.fr
+prd.fr
+presse.fr
+tm.fr
+aeroport.fr
+assedic.fr
+avocat.fr
+avoues.fr
+cci.fr
+chambagri.fr
+chirurgiens-dentistes.fr
+experts-comptables.fr
+geometre-expert.fr
+gouv.fr
+greta.fr
+huissier-justice.fr
+medecin.fr
+notaires.fr
+pharmacien.fr
+port.fr
+veterinaire.fr
+#ga
+#gd
+#ge
+com.ge
+edu.ge
+gov.ge
+org.ge
+mil.ge
+net.ge
+pvt.ge
+#gf
+#gg
+co.gg
+org.gg
+net.gg
+sch.gg
+gov.gg
+#gh
+com.gh
+edu.gh
+gov.gh
+org.gh
+mil.gh
+#gi
+com.gi
+ltd.gi
+gov.gi
+mod.gi
+edu.gi
+org.gi
+#gl
+#gm
+ac.gn
+com.gn
+edu.gn
+gov.gn
+org.gn
+net.gn
+#gov
+#gp
+com.gp
+net.gp
+mobi.gp
+edu.gp
+org.gp
+asso.gp
+#gq
+#gr
+com.gr
+edu.gr
+net.gr
+org.gr
+gov.gr
+#gs
+#gt
+com.gt
+edu.gt
+gob.gt
+ind.gt
+mil.gt
+net.gt
+org.gt
+#gu
+com.gu
+net.gu
+gov.gu
+org.gu
+edu.gu
+#gw
+#gy
+co.gy
+com.gy
+net.gy
+#hk
+com.hk
+edu.hk
+gov.hk
+idv.hk
+net.hk
+org.hk
+#hm
+#hn
+com.hn
+edu.hn
+org.hn
+net.hn
+mil.hn
+gob.hn
+#hr
+iz.hr
+from.hr
+name.hr
+com.hr
+#ht
+com.ht
+shop.ht
+firm.ht
+info.ht
+adult.ht
+net.ht
+pro.ht
+org.ht
+med.ht
+art.ht
+coop.ht
+pol.ht
+asso.ht
+edu.ht
+rel.ht
+gouv.ht
+perso.ht
+#hu
+co.hu
+info.hu
+org.hu
+priv.hu
+sport.hu
+tm.hu
+2000.hu
+agrar.hu
+bolt.hu
+casino.hu
+city.hu
+erotica.hu
+erotika.hu
+film.hu
+forum.hu
+games.hu
+hotel.hu
+ingatlan.hu
+jogasz.hu
+konyvelo.hu
+lakas.hu
+media.hu
+news.hu
+reklam.hu
+sex.hu
+shop.hu
+suli.hu
+szex.hu
+tozsde.hu
+utazas.hu
+video.hu
+#id
+ac.id
+biz.id
+co.id
+go.id
+mil.id
+my.id
+net.id
+or.id
+sch.id
+web.id
+#ie
+gov.ie
+#il
+ac.il
+co.il
+org.il
+net.il
+k12.il
+gov.il
+muni.il
+idf.il
+#im
+co.im
+ltd.co.im
+plc.co.im
+net.im
+gov.im
+org.im
+nic.im
+ac.im
+#in
+co.in
+firm.in
+net.in
+org.in
+gen.in
+ind.in
+nic.in
+ac.in
+edu.in
+res.in
+gov.in
+mil.in
+#info
+#int
+eu.int
+#io
+com.io
+#iq
+gov.iq
+edu.iq
+mil.iq
+com.iq
+org.iq
+net.iq
+#ir
+ac.ir
+co.ir
+gov.ir
+id.ir
+net.ir
+org.ir
+sch.ir
+#is
+net.is
+com.is
+edu.is
+gov.is
+org.is
+int.is
+#it
+gov.it
+edu.it
+agrigento.it
+ag.it
+alessandria.it
+al.it
+ancona.it
+an.it
+aosta.it
+aoste.it
+ao.it
+arezzo.it
+ar.it
+ascoli-piceno.it
+ascolipiceno.it
+ap.it
+asti.it
+at.it
+avellino.it
+av.it
+bari.it
+ba.it
+andria-barletta-trani.it
+andriabarlettatrani.it
+trani-barletta-andria.it
+tranibarlettaandria.it
+barletta-trani-andria.it
+barlettatraniandria.it
+andria-trani-barletta.it
+andriatranibarletta.it
+trani-andria-barletta.it
+traniandriabarletta.it
+bt.it
+belluno.it
+bl.it
+benevento.it
+bn.it
+bergamo.it
+bg.it
+biella.it
+bi.it
+bologna.it
+bo.it
+bolzano.it
+bozen.it
+balsan.it
+alto-adige.it
+altoadige.it
+suedtirol.it
+bz.it
+brescia.it
+bs.it
+brindisi.it
+br.it
+cagliari.it
+ca.it
+caltanissetta.it
+cl.it
+campobasso.it
+cb.it
+carboniaiglesias.it
+carbonia-iglesias.it
+iglesias-carbonia.it
+iglesiascarbonia.it
+ci.it
+caserta.it
+ce.it
+catania.it
+ct.it
+catanzaro.it
+cz.it
+chieti.it
+ch.it
+como.it
+co.it
+cosenza.it
+cs.it
+cremona.it
+cr.it
+crotone.it
+kr.it
+cuneo.it
+cn.it
+dell-ogliastra.it
+dellogliastra.it
+ogliastra.it
+og.it
+enna.it
+en.it
+ferrara.it
+fe.it
+fermo.it
+fm.it
+firenze.it
+florence.it
+fi.it
+foggia.it
+fg.it
+forli-cesena.it
+forlicesena.it
+cesena-forli.it
+cesenaforli.it
+fc.it
+frosinone.it
+fr.it
+genova.it
+genoa.it
+ge.it
+gorizia.it
+go.it
+grosseto.it
+gr.it
+imperia.it
+im.it
+isernia.it
+is.it
+laquila.it
+aquila.it
+aq.it
+la-spezia.it
+laspezia.it
+sp.it
+latina.it
+lt.it
+lecce.it
+le.it
+lecco.it
+lc.it
+livorno.it
+li.it
+lodi.it
+lo.it
+lucca.it
+lu.it
+macerata.it
+mc.it
+mantova.it
+mn.it
+massa-carrara.it
+massacarrara.it
+carrara-massa.it
+carraramassa.it
+ms.it
+matera.it
+mt.it
+medio-campidano.it
+mediocampidano.it
+campidano-medio.it
+campidanomedio.it
+vs.it
+messina.it
+me.it
+milano.it
+milan.it
+mi.it
+modena.it
+mo.it
+monza.it
+monza-brianza.it
+monzabrianza.it
+monzaebrianza.it
+monzaedellabrianza.it
+monza-e-della-brianza.it
+mb.it
+napoli.it
+naples.it
+na.it
+novara.it
+no.it
+nuoro.it
+nu.it
+oristano.it
+or.it
+padova.it
+padua.it
+pd.it
+palermo.it
+pa.it
+parma.it
+pr.it
+pavia.it
+pv.it
+perugia.it
+pg.it
+pescara.it
+pe.it
+pesaro-urbino.it
+pesarourbino.it
+urbino-pesaro.it
+urbinopesaro.it
+pu.it
+piacenza.it
+pc.it
+pisa.it
+pi.it
+pistoia.it
+pt.it
+pordenone.it
+pn.it
+potenza.it
+pz.it
+prato.it
+po.it
+ragusa.it
+rg.it
+ravenna.it
+ra.it
+reggio-calabria.it
+reggiocalabria.it
+rc.it
+reggio-emilia.it
+reggioemilia.it
+re.it
+rieti.it
+ri.it
+rimini.it
+rn.it
+roma.it
+rome.it
+rm.it
+rovigo.it
+ro.it
+salerno.it
+sa.it
+sassari.it
+ss.it
+savona.it
+sv.it
+siena.it
+si.it
+siracusa.it
+sr.it
+sondrio.it
+so.it
+taranto.it
+ta.it
+tempio-olbia.it
+tempioolbia.it
+olbia-tempio.it
+olbiatempio.it
+ot.it
+teramo.it
+te.it
+terni.it
+tr.it
+torino.it
+turin.it
+to.it
+trapani.it
+tp.it
+trento.it
+trentino.it
+tn.it
+treviso.it
+tv.it
+trieste.it
+ts.it
+udine.it
+ud.it
+varese.it
+va.it
+venezia.it
+venice.it
+ve.it
+verbania.it
+vb.it
+vercelli.it
+vc.it
+verona.it
+vr.it
+vibo-valentia.it
+vibovalentia.it
+vv.it
+vicenza.it
+vi.it
+viterbo.it
+vt.it
+#je
+co.je
+org.je
+net.je
+sch.je
+gov.je
+#jm
+edu.jm
+gov.jm
+com.jm
+net.jm
+org.jm
+#jo
+com.jo
+org.jo
+net.jo
+edu.jo
+sch.jo
+gov.jo
+mil.jo
+name.jo
+#jobs
+#jp
+ac.jp
+ad.jp
+co.jp
+ed.jp
+go.jp
+gr.jp
+lg.jp
+ne.jp
+or.jp
+aichi.jp
+akita.jp
+aomori.jp
+chiba.jp
+ehime.jp
+fukui.jp
+fukuoka.jp
+fukushima.jp
+gifu.jp
+gunma.jp
+hiroshima.jp
+hokkaido.jp
+hyogo.jp
+ibaraki.jp
+ishikawa.jp
+iwate.jp
+kagawa.jp
+kagoshima.jp
+kanagawa.jp
+kochi.jp
+kumamoto.jp
+kyoto.jp
+mie.jp
+miyagi.jp
+miyazaki.jp
+nagano.jp
+nagasaki.jp
+nara.jp
+niigata.jp
+oita.jp
+okayama.jp
+okinawa.jp
+osaka.jp
+saga.jp
+saitama.jp
+shiga.jp
+shimane.jp
+shizuoka.jp
+tochigi.jp
+tokushima.jp
+tokyo.jp
+tottori.jp
+toyama.jp
+wakayama.jp
+yamagata.jp
+yamaguchi.jp
+yamanashi.jp
+kawasaki.jp
+kitakyushu.jp
+kobe.jp
+nagoya.jp
+sapporo.jp
+sendai.jp
+yokohama.jp
+aisai.aichi.jp
+ama.aichi.jp
+anjo.aichi.jp
+asuke.aichi.jp
+chiryu.aichi.jp
+chita.aichi.jp
+fuso.aichi.jp
+gamagori.aichi.jp
+handa.aichi.jp
+hazu.aichi.jp
+hekinan.aichi.jp
+higashiura.aichi.jp
+ichinomiya.aichi.jp
+inazawa.aichi.jp
+inuyama.aichi.jp
+isshiki.aichi.jp
+iwakura.aichi.jp
+kanie.aichi.jp
+kariya.aichi.jp
+kasugai.aichi.jp
+kira.aichi.jp
+kiyosu.aichi.jp
+komaki.aichi.jp
+konan.aichi.jp
+kota.aichi.jp
+mihama.aichi.jp
+miyoshi.aichi.jp
+nagakute.aichi.jp
+nishio.aichi.jp
+nisshin.aichi.jp
+obu.aichi.jp
+oguchi.aichi.jp
+oharu.aichi.jp
+okazaki.aichi.jp
+owariasahi.aichi.jp
+seto.aichi.jp
+shikatsu.aichi.jp
+shinshiro.aichi.jp
+shitara.aichi.jp
+tahara.aichi.jp
+takahama.aichi.jp
+tobishima.aichi.jp
+toei.aichi.jp
+togo.aichi.jp
+tokai.aichi.jp
+tokoname.aichi.jp
+toyoake.aichi.jp
+toyohashi.aichi.jp
+toyokawa.aichi.jp
+toyone.aichi.jp
+toyota.aichi.jp
+tsushima.aichi.jp
+yatomi.aichi.jp
+akita.akita.jp
+daisen.akita.jp
+fujisato.akita.jp
+gojome.akita.jp
+hachirogata.akita.jp
+happou.akita.jp
+higashinaruse.akita.jp
+honjo.akita.jp
+honjyo.akita.jp
+ikawa.akita.jp
+kamikoani.akita.jp
+kamioka.akita.jp
+katagami.akita.jp
+kazuno.akita.jp
+kitaakita.akita.jp
+kosaka.akita.jp
+kyowa.akita.jp
+misato.akita.jp
+mitane.akita.jp
+moriyoshi.akita.jp
+nikaho.akita.jp
+noshiro.akita.jp
+odate.akita.jp
+oga.akita.jp
+ogata.akita.jp
+semboku.akita.jp
+yokote.akita.jp
+yurihonjo.akita.jp
+aomori.aomori.jp
+gonohe.aomori.jp
+hachinohe.aomori.jp
+hashikami.aomori.jp
+hiranai.aomori.jp
+hirosaki.aomori.jp
+itayanagi.aomori.jp
+kuroishi.aomori.jp
+misawa.aomori.jp
+mutsu.aomori.jp
+nakadomari.aomori.jp
+noheji.aomori.jp
+oirase.aomori.jp
+owani.aomori.jp
+rokunohe.aomori.jp
+sannohe.aomori.jp
+shichinohe.aomori.jp
+shingo.aomori.jp
+takko.aomori.jp
+towada.aomori.jp
+tsugaru.aomori.jp
+tsuruta.aomori.jp
+abiko.chiba.jp
+asahi.chiba.jp
+chonan.chiba.jp
+chosei.chiba.jp
+choshi.chiba.jp
+chuo.chiba.jp
+funabashi.chiba.jp
+futtsu.chiba.jp
+hanamigawa.chiba.jp
+ichihara.chiba.jp
+ichikawa.chiba.jp
+ichinomiya.chiba.jp
+inzai.chiba.jp
+isumi.chiba.jp
+kamagaya.chiba.jp
+kamogawa.chiba.jp
+kashiwa.chiba.jp
+katori.chiba.jp
+katsuura.chiba.jp
+kimitsu.chiba.jp
+kisarazu.chiba.jp
+kozaki.chiba.jp
+kujukuri.chiba.jp
+kyonan.chiba.jp
+matsudo.chiba.jp
+midori.chiba.jp
+mihama.chiba.jp
+minamiboso.chiba.jp
+mobara.chiba.jp
+mutsuzawa.chiba.jp
+nagara.chiba.jp
+nagareyama.chiba.jp
+narashino.chiba.jp
+narita.chiba.jp
+noda.chiba.jp
+oamishirasato.chiba.jp
+omigawa.chiba.jp
+onjuku.chiba.jp
+otaki.chiba.jp
+sakae.chiba.jp
+sakura.chiba.jp
+shimofusa.chiba.jp
+shirako.chiba.jp
+shiroi.chiba.jp
+shisui.chiba.jp
+sodegaura.chiba.jp
+sosa.chiba.jp
+tako.chiba.jp
+tateyama.chiba.jp
+togane.chiba.jp
+tohnosho.chiba.jp
+tomisato.chiba.jp
+urayasu.chiba.jp
+yachimata.chiba.jp
+yachiyo.chiba.jp
+yokaichiba.chiba.jp
+yokoshibahikari.chiba.jp
+yotsukaido.chiba.jp
+ainan.ehime.jp
+honai.ehime.jp
+ikata.ehime.jp
+imabari.ehime.jp
+iyo.ehime.jp
+kamijima.ehime.jp
+kihoku.ehime.jp
+kumakogen.ehime.jp
+masaki.ehime.jp
+matsuno.ehime.jp
+matsuyama.ehime.jp
+namikata.ehime.jp
+niihama.ehime.jp
+ozu.ehime.jp
+saijo.ehime.jp
+seiyo.ehime.jp
+shikokuchuo.ehime.jp
+tobe.ehime.jp
+toon.ehime.jp
+uchiko.ehime.jp
+uwajima.ehime.jp
+yawatahama.ehime.jp
+echizen.fukui.jp
+eiheiji.fukui.jp
+fukui.fukui.jp
+ikeda.fukui.jp
+katsuyama.fukui.jp
+mihama.fukui.jp
+minamiechizen.fukui.jp
+obama.fukui.jp
+ohi.fukui.jp
+ono.fukui.jp
+sabae.fukui.jp
+sakai.fukui.jp
+takahama.fukui.jp
+tsuruga.fukui.jp
+wakasa.fukui.jp
+ashiya.fukuoka.jp
+buzen.fukuoka.jp
+chikugo.fukuoka.jp
+chikuho.fukuoka.jp
+chikujo.fukuoka.jp
+chikushino.fukuoka.jp
+chikuzen.fukuoka.jp
+chuo.fukuoka.jp
+dazaifu.fukuoka.jp
+fukuchi.fukuoka.jp
+hakata.fukuoka.jp
+higashi.fukuoka.jp
+hirokawa.fukuoka.jp
+hisayama.fukuoka.jp
+iizuka.fukuoka.jp
+inatsuki.fukuoka.jp
+kaho.fukuoka.jp
+kasuga.fukuoka.jp
+kasuya.fukuoka.jp
+kawara.fukuoka.jp
+keisen.fukuoka.jp
+koga.fukuoka.jp
+kurate.fukuoka.jp
+kurogi.fukuoka.jp
+kurume.fukuoka.jp
+minami.fukuoka.jp
+miyako.fukuoka.jp
+miyama.fukuoka.jp
+miyawaka.fukuoka.jp
+mizumaki.fukuoka.jp
+munakata.fukuoka.jp
+nakagawa.fukuoka.jp
+nakama.fukuoka.jp
+nishi.fukuoka.jp
+nogata.fukuoka.jp
+ogori.fukuoka.jp
+okagaki.fukuoka.jp
+okawa.fukuoka.jp
+oki.fukuoka.jp
+omuta.fukuoka.jp
+onga.fukuoka.jp
+onojo.fukuoka.jp
+oto.fukuoka.jp
+saigawa.fukuoka.jp
+sasaguri.fukuoka.jp
+shingu.fukuoka.jp
+shinyoshitomi.fukuoka.jp
+shonai.fukuoka.jp
+soeda.fukuoka.jp
+sue.fukuoka.jp
+tachiarai.fukuoka.jp
+tagawa.fukuoka.jp
+takata.fukuoka.jp
+toho.fukuoka.jp
+toyotsu.fukuoka.jp
+tsuiki.fukuoka.jp
+ukiha.fukuoka.jp
+umi.fukuoka.jp
+usui.fukuoka.jp
+yamada.fukuoka.jp
+yame.fukuoka.jp
+yanagawa.fukuoka.jp
+yukuhashi.fukuoka.jp
+aizubange.fukushima.jp
+aizumisato.fukushima.jp
+aizuwakamatsu.fukushima.jp
+asakawa.fukushima.jp
+bandai.fukushima.jp
+date.fukushima.jp
+fukushima.fukushima.jp
+furudono.fukushima.jp
+futaba.fukushima.jp
+hanawa.fukushima.jp
+higashi.fukushima.jp
+hirata.fukushima.jp
+hirono.fukushima.jp
+iitate.fukushima.jp
+inawashiro.fukushima.jp
+ishikawa.fukushima.jp
+iwaki.fukushima.jp
+izumizaki.fukushima.jp
+kagamiishi.fukushima.jp
+kaneyama.fukushima.jp
+kawamata.fukushima.jp
+kitakata.fukushima.jp
+kitashiobara.fukushima.jp
+koori.fukushima.jp
+koriyama.fukushima.jp
+kunimi.fukushima.jp
+miharu.fukushima.jp
+mishima.fukushima.jp
+namie.fukushima.jp
+nango.fukushima.jp
+nishiaizu.fukushima.jp
+nishigo.fukushima.jp
+okuma.fukushima.jp
+omotego.fukushima.jp
+ono.fukushima.jp
+otama.fukushima.jp
+samegawa.fukushima.jp
+shimogo.fukushima.jp
+shirakawa.fukushima.jp
+showa.fukushima.jp
+soma.fukushima.jp
+sukagawa.fukushima.jp
+taishin.fukushima.jp
+tamakawa.fukushima.jp
+tanagura.fukushima.jp
+tenei.fukushima.jp
+yabuki.fukushima.jp
+yamato.fukushima.jp
+yamatsuri.fukushima.jp
+yanaizu.fukushima.jp
+yugawa.fukushima.jp
+anpachi.gifu.jp
+ena.gifu.jp
+gifu.gifu.jp
+ginan.gifu.jp
+godo.gifu.jp
+gujo.gifu.jp
+hashima.gifu.jp
+hichiso.gifu.jp
+hida.gifu.jp
+higashishirakawa.gifu.jp
+ibigawa.gifu.jp
+ikeda.gifu.jp
+kakamigahara.gifu.jp
+kani.gifu.jp
+kasahara.gifu.jp
+kasamatsu.gifu.jp
+kawaue.gifu.jp
+kitagata.gifu.jp
+mino.gifu.jp
+minokamo.gifu.jp
+mitake.gifu.jp
+mizunami.gifu.jp
+motosu.gifu.jp
+nakatsugawa.gifu.jp
+ogaki.gifu.jp
+sakahogi.gifu.jp
+seki.gifu.jp
+sekigahara.gifu.jp
+shirakawa.gifu.jp
+tajimi.gifu.jp
+takayama.gifu.jp
+tarui.gifu.jp
+toki.gifu.jp
+tomika.gifu.jp
+wanouchi.gifu.jp
+yamagata.gifu.jp
+yaotsu.gifu.jp
+yoro.gifu.jp
+annaka.gunma.jp
+chiyoda.gunma.jp
+fujioka.gunma.jp
+higashiagatsuma.gunma.jp
+isesaki.gunma.jp
+itakura.gunma.jp
+kanna.gunma.jp
+kanra.gunma.jp
+katashina.gunma.jp
+kawaba.gunma.jp
+kiryu.gunma.jp
+kusatsu.gunma.jp
+maebashi.gunma.jp
+meiwa.gunma.jp
+midori.gunma.jp
+minakami.gunma.jp
+naganohara.gunma.jp
+nakanojo.gunma.jp
+nanmoku.gunma.jp
+numata.gunma.jp
+oizumi.gunma.jp
+ora.gunma.jp
+ota.gunma.jp
+shibukawa.gunma.jp
+shimonita.gunma.jp
+shinto.gunma.jp
+showa.gunma.jp
+takasaki.gunma.jp
+takayama.gunma.jp
+tamamura.gunma.jp
+tatebayashi.gunma.jp
+tomioka.gunma.jp
+tsukiyono.gunma.jp
+tsumagoi.gunma.jp
+ueno.gunma.jp
+yoshioka.gunma.jp
+asaminami.hiroshima.jp
+daiwa.hiroshima.jp
+etajima.hiroshima.jp
+fuchu.hiroshima.jp
+fukuyama.hiroshima.jp
+hatsukaichi.hiroshima.jp
+higashihiroshima.hiroshima.jp
+hongo.hiroshima.jp
+jinsekikogen.hiroshima.jp
+kaita.hiroshima.jp
+kui.hiroshima.jp
+kumano.hiroshima.jp
+kure.hiroshima.jp
+mihara.hiroshima.jp
+miyoshi.hiroshima.jp
+naka.hiroshima.jp
+onomichi.hiroshima.jp
+osakikamijima.hiroshima.jp
+otake.hiroshima.jp
+saka.hiroshima.jp
+sera.hiroshima.jp
+seranishi.hiroshima.jp
+shinichi.hiroshima.jp
+shobara.hiroshima.jp
+takehara.hiroshima.jp
+abashiri.hokkaido.jp
+abira.hokkaido.jp
+aibetsu.hokkaido.jp
+akabira.hokkaido.jp
+akkeshi.hokkaido.jp
+asahikawa.hokkaido.jp
+ashibetsu.hokkaido.jp
+ashoro.hokkaido.jp
+assabu.hokkaido.jp
+atsuma.hokkaido.jp
+bibai.hokkaido.jp
+biei.hokkaido.jp
+bifuka.hokkaido.jp
+bihoro.hokkaido.jp
+biratori.hokkaido.jp
+chippubetsu.hokkaido.jp
+chitose.hokkaido.jp
+date.hokkaido.jp
+ebetsu.hokkaido.jp
+embetsu.hokkaido.jp
+eniwa.hokkaido.jp
+erimo.hokkaido.jp
+esan.hokkaido.jp
+esashi.hokkaido.jp
+fukagawa.hokkaido.jp
+fukushima.hokkaido.jp
+furano.hokkaido.jp
+furubira.hokkaido.jp
+haboro.hokkaido.jp
+hakodate.hokkaido.jp
+hamatonbetsu.hokkaido.jp
+hidaka.hokkaido.jp
+higashikagura.hokkaido.jp
+higashikawa.hokkaido.jp
+hiroo.hokkaido.jp
+hokuryu.hokkaido.jp
+hokuto.hokkaido.jp
+honbetsu.hokkaido.jp
+horokanai.hokkaido.jp
+horonobe.hokkaido.jp
+ikeda.hokkaido.jp
+imakane.hokkaido.jp
+ishikari.hokkaido.jp
+iwamizawa.hokkaido.jp
+iwanai.hokkaido.jp
+kamifurano.hokkaido.jp
+kamikawa.hokkaido.jp
+kamishihoro.hokkaido.jp
+kamisunagawa.hokkaido.jp
+kamoenai.hokkaido.jp
+kayabe.hokkaido.jp
+kembuchi.hokkaido.jp
+kikonai.hokkaido.jp
+kimobetsu.hokkaido.jp
+kitahiroshima.hokkaido.jp
+kitami.hokkaido.jp
+kiyosato.hokkaido.jp
+koshimizu.hokkaido.jp
+kunneppu.hokkaido.jp
+kuriyama.hokkaido.jp
+kuromatsunai.hokkaido.jp
+kushiro.hokkaido.jp
+kutchan.hokkaido.jp
+kyowa.hokkaido.jp
+mashike.hokkaido.jp
+matsumae.hokkaido.jp
+mikasa.hokkaido.jp
+minamifurano.hokkaido.jp
+mombetsu.hokkaido.jp
+moseushi.hokkaido.jp
+mukawa.hokkaido.jp
+muroran.hokkaido.jp
+naie.hokkaido.jp
+nakagawa.hokkaido.jp
+nakasatsunai.hokkaido.jp
+nakatombetsu.hokkaido.jp
+nanae.hokkaido.jp
+nanporo.hokkaido.jp
+nayoro.hokkaido.jp
+nemuro.hokkaido.jp
+niikappu.hokkaido.jp
+niki.hokkaido.jp
+nishiokoppe.hokkaido.jp
+noboribetsu.hokkaido.jp
+numata.hokkaido.jp
+obihiro.hokkaido.jp
+obira.hokkaido.jp
+oketo.hokkaido.jp
+okoppe.hokkaido.jp
+otaru.hokkaido.jp
+otobe.hokkaido.jp
+otofuke.hokkaido.jp
+otoineppu.hokkaido.jp
+oumu.hokkaido.jp
+ozora.hokkaido.jp
+pippu.hokkaido.jp
+rankoshi.hokkaido.jp
+rebun.hokkaido.jp
+rikubetsu.hokkaido.jp
+rishiri.hokkaido.jp
+rishirifuji.hokkaido.jp
+saroma.hokkaido.jp
+sarufutsu.hokkaido.jp
+shakotan.hokkaido.jp
+shari.hokkaido.jp
+shibecha.hokkaido.jp
+shibetsu.hokkaido.jp
+shikabe.hokkaido.jp
+shikaoi.hokkaido.jp
+shimamaki.hokkaido.jp
+shimizu.hokkaido.jp
+shimokawa.hokkaido.jp
+shinshinotsu.hokkaido.jp
+shintoku.hokkaido.jp
+shiranuka.hokkaido.jp
+shiraoi.hokkaido.jp
+shiriuchi.hokkaido.jp
+sobetsu.hokkaido.jp
+sunagawa.hokkaido.jp
+taiki.hokkaido.jp
+takasu.hokkaido.jp
+takikawa.hokkaido.jp
+takinoue.hokkaido.jp
+teshikaga.hokkaido.jp
+tobetsu.hokkaido.jp
+tohma.hokkaido.jp
+tomakomai.hokkaido.jp
+tomari.hokkaido.jp
+toya.hokkaido.jp
+toyako.hokkaido.jp
+toyotomi.hokkaido.jp
+toyoura.hokkaido.jp
+tsubetsu.hokkaido.jp
+tsukigata.hokkaido.jp
+urakawa.hokkaido.jp
+urausu.hokkaido.jp
+uryu.hokkaido.jp
+utashinai.hokkaido.jp
+wakkanai.hokkaido.jp
+wassamu.hokkaido.jp
+yakumo.hokkaido.jp
+yoichi.hokkaido.jp
+aioi.hyogo.jp
+akashi.hyogo.jp
+ako.hyogo.jp
+amagasaki.hyogo.jp
+aogaki.hyogo.jp
+asago.hyogo.jp
+ashiya.hyogo.jp
+awaji.hyogo.jp
+fukusaki.hyogo.jp
+goshiki.hyogo.jp
+harima.hyogo.jp
+himeji.hyogo.jp
+ichikawa.hyogo.jp
+inagawa.hyogo.jp
+itami.hyogo.jp
+kakogawa.hyogo.jp
+kamigori.hyogo.jp
+kamikawa.hyogo.jp
+kasai.hyogo.jp
+kasuga.hyogo.jp
+kawanishi.hyogo.jp
+miki.hyogo.jp
+minamiawaji.hyogo.jp
+nishinomiya.hyogo.jp
+nishiwaki.hyogo.jp
+ono.hyogo.jp
+sanda.hyogo.jp
+sannan.hyogo.jp
+sasayama.hyogo.jp
+sayo.hyogo.jp
+shingu.hyogo.jp
+shinonsen.hyogo.jp
+shiso.hyogo.jp
+sumoto.hyogo.jp
+taishi.hyogo.jp
+taka.hyogo.jp
+takarazuka.hyogo.jp
+takasago.hyogo.jp
+takino.hyogo.jp
+tamba.hyogo.jp
+tatsuno.hyogo.jp
+toyooka.hyogo.jp
+yabu.hyogo.jp
+yashiro.hyogo.jp
+yoka.hyogo.jp
+yokawa.hyogo.jp
+ami.ibaraki.jp
+asahi.ibaraki.jp
+bando.ibaraki.jp
+chikusei.ibaraki.jp
+daigo.ibaraki.jp
+fujishiro.ibaraki.jp
+hitachi.ibaraki.jp
+hitachinaka.ibaraki.jp
+hitachiomiya.ibaraki.jp
+hitachiota.ibaraki.jp
+ibaraki.ibaraki.jp
+ina.ibaraki.jp
+inashiki.ibaraki.jp
+itako.ibaraki.jp
+iwama.ibaraki.jp
+joso.ibaraki.jp
+kamisu.ibaraki.jp
+kasama.ibaraki.jp
+kashima.ibaraki.jp
+kasumigaura.ibaraki.jp
+koga.ibaraki.jp
+miho.ibaraki.jp
+mito.ibaraki.jp
+moriya.ibaraki.jp
+naka.ibaraki.jp
+namegata.ibaraki.jp
+oarai.ibaraki.jp
+ogawa.ibaraki.jp
+omitama.ibaraki.jp
+ryugasaki.ibaraki.jp
+sakai.ibaraki.jp
+sakuragawa.ibaraki.jp
+shimodate.ibaraki.jp
+shimotsuma.ibaraki.jp
+shirosato.ibaraki.jp
+sowa.ibaraki.jp
+suifu.ibaraki.jp
+takahagi.ibaraki.jp
+tamatsukuri.ibaraki.jp
+tokai.ibaraki.jp
+tomobe.ibaraki.jp
+tone.ibaraki.jp
+toride.ibaraki.jp
+tsuchiura.ibaraki.jp
+tsukuba.ibaraki.jp
+uchihara.ibaraki.jp
+ushiku.ibaraki.jp
+yachiyo.ibaraki.jp
+yamagata.ibaraki.jp
+yawara.ibaraki.jp
+yuki.ibaraki.jp
+anamizu.ishikawa.jp
+hakui.ishikawa.jp
+hakusan.ishikawa.jp
+kaga.ishikawa.jp
+kahoku.ishikawa.jp
+kanazawa.ishikawa.jp
+kawakita.ishikawa.jp
+komatsu.ishikawa.jp
+nakanoto.ishikawa.jp
+nanao.ishikawa.jp
+nomi.ishikawa.jp
+nonoichi.ishikawa.jp
+noto.ishikawa.jp
+shika.ishikawa.jp
+suzu.ishikawa.jp
+tsubata.ishikawa.jp
+tsurugi.ishikawa.jp
+uchinada.ishikawa.jp
+wajima.ishikawa.jp
+fudai.iwate.jp
+fujisawa.iwate.jp
+hanamaki.iwate.jp
+hiraizumi.iwate.jp
+hirono.iwate.jp
+ichinohe.iwate.jp
+ichinoseki.iwate.jp
+iwaizumi.iwate.jp
+iwate.iwate.jp
+joboji.iwate.jp
+kamaishi.iwate.jp
+kanegasaki.iwate.jp
+karumai.iwate.jp
+kawai.iwate.jp
+kitakami.iwate.jp
+kuji.iwate.jp
+kunohe.iwate.jp
+kuzumaki.iwate.jp
+miyako.iwate.jp
+mizusawa.iwate.jp
+morioka.iwate.jp
+ninohe.iwate.jp
+noda.iwate.jp
+ofunato.iwate.jp
+oshu.iwate.jp
+otsuchi.iwate.jp
+rikuzentakata.iwate.jp
+shiwa.iwate.jp
+shizukuishi.iwate.jp
+sumita.iwate.jp
+takizawa.iwate.jp
+tanohata.iwate.jp
+tono.iwate.jp
+yahaba.iwate.jp
+yamada.iwate.jp
+ayagawa.kagawa.jp
+higashikagawa.kagawa.jp
+kanonji.kagawa.jp
+kotohira.kagawa.jp
+manno.kagawa.jp
+marugame.kagawa.jp
+mitoyo.kagawa.jp
+naoshima.kagawa.jp
+sanuki.kagawa.jp
+tadotsu.kagawa.jp
+takamatsu.kagawa.jp
+tonosho.kagawa.jp
+uchinomi.kagawa.jp
+utazu.kagawa.jp
+zentsuji.kagawa.jp
+akune.kagoshima.jp
+amami.kagoshima.jp
+hioki.kagoshima.jp
+isa.kagoshima.jp
+isen.kagoshima.jp
+izumi.kagoshima.jp
+kagoshima.kagoshima.jp
+kanoya.kagoshima.jp
+kawanabe.kagoshima.jp
+kinko.kagoshima.jp
+kouyama.kagoshima.jp
+makurazaki.kagoshima.jp
+matsumoto.kagoshima.jp
+minamitane.kagoshima.jp
+nakatane.kagoshima.jp
+nishinoomote.kagoshima.jp
+satsumasendai.kagoshima.jp
+soo.kagoshima.jp
+tarumizu.kagoshima.jp
+yusui.kagoshima.jp
+aikawa.kanagawa.jp
+atsugi.kanagawa.jp
+ayase.kanagawa.jp
+chigasaki.kanagawa.jp
+ebina.kanagawa.jp
+fujisawa.kanagawa.jp
+hadano.kanagawa.jp
+hakone.kanagawa.jp
+hiratsuka.kanagawa.jp
+isehara.kanagawa.jp
+kaisei.kanagawa.jp
+kamakura.kanagawa.jp
+kiyokawa.kanagawa.jp
+matsuda.kanagawa.jp
+minamiashigara.kanagawa.jp
+miura.kanagawa.jp
+nakai.kanagawa.jp
+ninomiya.kanagawa.jp
+odawara.kanagawa.jp
+oi.kanagawa.jp
+oiso.kanagawa.jp
+sagamihara.kanagawa.jp
+samukawa.kanagawa.jp
+tsukui.kanagawa.jp
+yamakita.kanagawa.jp
+yamato.kanagawa.jp
+yokosuka.kanagawa.jp
+yugawara.kanagawa.jp
+zama.kanagawa.jp
+zushi.kanagawa.jp
+aki.kochi.jp
+geisei.kochi.jp
+hidaka.kochi.jp
+higashitsuno.kochi.jp
+ino.kochi.jp
+kagami.kochi.jp
+kami.kochi.jp
+kitagawa.kochi.jp
+kochi.kochi.jp
+mihara.kochi.jp
+motoyama.kochi.jp
+muroto.kochi.jp
+nahari.kochi.jp
+nakamura.kochi.jp
+nankoku.kochi.jp
+nishitosa.kochi.jp
+niyodogawa.kochi.jp
+ochi.kochi.jp
+okawa.kochi.jp
+otoyo.kochi.jp
+otsuki.kochi.jp
+sakawa.kochi.jp
+sukumo.kochi.jp
+susaki.kochi.jp
+tosa.kochi.jp
+tosashimizu.kochi.jp
+toyo.kochi.jp
+tsuno.kochi.jp
+umaji.kochi.jp
+yasuda.kochi.jp
+yusuhara.kochi.jp
+amakusa.kumamoto.jp
+arao.kumamoto.jp
+aso.kumamoto.jp
+choyo.kumamoto.jp
+gyokuto.kumamoto.jp
+hitoyoshi.kumamoto.jp
+kamiamakusa.kumamoto.jp
+kashima.kumamoto.jp
+kikuchi.kumamoto.jp
+kosa.kumamoto.jp
+kumamoto.kumamoto.jp
+mashiki.kumamoto.jp
+mifune.kumamoto.jp
+minamata.kumamoto.jp
+minamioguni.kumamoto.jp
+nagasu.kumamoto.jp
+nishihara.kumamoto.jp
+oguni.kumamoto.jp
+ozu.kumamoto.jp
+sumoto.kumamoto.jp
+takamori.kumamoto.jp
+uki.kumamoto.jp
+uto.kumamoto.jp
+yamaga.kumamoto.jp
+yamato.kumamoto.jp
+yatsushiro.kumamoto.jp
+ayabe.kyoto.jp
+fukuchiyama.kyoto.jp
+higashiyama.kyoto.jp
+ide.kyoto.jp
+ine.kyoto.jp
+joyo.kyoto.jp
+kameoka.kyoto.jp
+kamo.kyoto.jp
+kita.kyoto.jp
+kizu.kyoto.jp
+kumiyama.kyoto.jp
+kyotamba.kyoto.jp
+kyotanabe.kyoto.jp
+kyotango.kyoto.jp
+maizuru.kyoto.jp
+minami.kyoto.jp
+minamiyamashiro.kyoto.jp
+miyazu.kyoto.jp
+muko.kyoto.jp
+nagaokakyo.kyoto.jp
+nakagyo.kyoto.jp
+nantan.kyoto.jp
+oyamazaki.kyoto.jp
+sakyo.kyoto.jp
+seika.kyoto.jp
+tanabe.kyoto.jp
+uji.kyoto.jp
+ujitawara.kyoto.jp
+wazuka.kyoto.jp
+yamashina.kyoto.jp
+yawata.kyoto.jp
+asahi.mie.jp
+inabe.mie.jp
+ise.mie.jp
+kameyama.mie.jp
+kawagoe.mie.jp
+kiho.mie.jp
+kisosaki.mie.jp
+kiwa.mie.jp
+komono.mie.jp
+kumano.mie.jp
+kuwana.mie.jp
+matsusaka.mie.jp
+meiwa.mie.jp
+mihama.mie.jp
+minamiise.mie.jp
+misugi.mie.jp
+miyama.mie.jp
+nabari.mie.jp
+shima.mie.jp
+suzuka.mie.jp
+tado.mie.jp
+taiki.mie.jp
+taki.mie.jp
+tamaki.mie.jp
+toba.mie.jp
+tsu.mie.jp
+udono.mie.jp
+ureshino.mie.jp
+watarai.mie.jp
+yokkaichi.mie.jp
+furukawa.miyagi.jp
+higashimatsushima.miyagi.jp
+ishinomaki.miyagi.jp
+iwanuma.miyagi.jp
+kakuda.miyagi.jp
+kami.miyagi.jp
+kawasaki.miyagi.jp
+kesennuma.miyagi.jp
+marumori.miyagi.jp
+matsushima.miyagi.jp
+minamisanriku.miyagi.jp
+misato.miyagi.jp
+murata.miyagi.jp
+natori.miyagi.jp
+ogawara.miyagi.jp
+ohira.miyagi.jp
+onagawa.miyagi.jp
+osaki.miyagi.jp
+rifu.miyagi.jp
+semine.miyagi.jp
+shibata.miyagi.jp
+shichikashuku.miyagi.jp
+shikama.miyagi.jp
+shiogama.miyagi.jp
+shiroishi.miyagi.jp
+tagajo.miyagi.jp
+taiwa.miyagi.jp
+tome.miyagi.jp
+tomiya.miyagi.jp
+wakuya.miyagi.jp
+watari.miyagi.jp
+yamamoto.miyagi.jp
+zao.miyagi.jp
+aya.miyazaki.jp
+ebino.miyazaki.jp
+gokase.miyazaki.jp
+hyuga.miyazaki.jp
+kadogawa.miyazaki.jp
+kawaminami.miyazaki.jp
+kijo.miyazaki.jp
+kitagawa.miyazaki.jp
+kitakata.miyazaki.jp
+kitaura.miyazaki.jp
+kobayashi.miyazaki.jp
+kunitomi.miyazaki.jp
+kushima.miyazaki.jp
+mimata.miyazaki.jp
+miyakonojo.miyazaki.jp
+miyazaki.miyazaki.jp
+morotsuka.miyazaki.jp
+nichinan.miyazaki.jp
+nishimera.miyazaki.jp
+nobeoka.miyazaki.jp
+saito.miyazaki.jp
+shiiba.miyazaki.jp
+shintomi.miyazaki.jp
+takaharu.miyazaki.jp
+takanabe.miyazaki.jp
+takazaki.miyazaki.jp
+tsuno.miyazaki.jp
+achi.nagano.jp
+agematsu.nagano.jp
+anan.nagano.jp
+aoki.nagano.jp
+asahi.nagano.jp
+azumino.nagano.jp
+chikuhoku.nagano.jp
+chikuma.nagano.jp
+chino.nagano.jp
+fujimi.nagano.jp
+hakuba.nagano.jp
+hara.nagano.jp
+hiraya.nagano.jp
+iida.nagano.jp
+iijima.nagano.jp
+iiyama.nagano.jp
+iizuna.nagano.jp
+ikeda.nagano.jp
+ikusaka.nagano.jp
+ina.nagano.jp
+karuizawa.nagano.jp
+kawakami.nagano.jp
+kiso.nagano.jp
+kisofukushima.nagano.jp
+kitaaiki.nagano.jp
+komagane.nagano.jp
+komoro.nagano.jp
+matsukawa.nagano.jp
+matsumoto.nagano.jp
+miasa.nagano.jp
+minamiaiki.nagano.jp
+minamimaki.nagano.jp
+minamiminowa.nagano.jp
+minowa.nagano.jp
+miyada.nagano.jp
+miyota.nagano.jp
+mochizuki.nagano.jp
+nagano.nagano.jp
+nagawa.nagano.jp
+nagiso.nagano.jp
+nakagawa.nagano.jp
+nakano.nagano.jp
+nozawaonsen.nagano.jp
+obuse.nagano.jp
+ogawa.nagano.jp
+okaya.nagano.jp
+omachi.nagano.jp
+omi.nagano.jp
+ookuwa.nagano.jp
+ooshika.nagano.jp
+otaki.nagano.jp
+otari.nagano.jp
+sakae.nagano.jp
+sakaki.nagano.jp
+saku.nagano.jp
+sakuho.nagano.jp
+shimosuwa.nagano.jp
+shinanomachi.nagano.jp
+shiojiri.nagano.jp
+suwa.nagano.jp
+suzaka.nagano.jp
+takagi.nagano.jp
+takamori.nagano.jp
+takayama.nagano.jp
+tateshina.nagano.jp
+tatsuno.nagano.jp
+togakushi.nagano.jp
+togura.nagano.jp
+tomi.nagano.jp
+ueda.nagano.jp
+wada.nagano.jp
+yamagata.nagano.jp
+yamanouchi.nagano.jp
+yasaka.nagano.jp
+yasuoka.nagano.jp
+chijiwa.nagasaki.jp
+futsu.nagasaki.jp
+goto.nagasaki.jp
+hasami.nagasaki.jp
+hirado.nagasaki.jp
+iki.nagasaki.jp
+isahaya.nagasaki.jp
+kawatana.nagasaki.jp
+kuchinotsu.nagasaki.jp
+matsuura.nagasaki.jp
+nagasaki.nagasaki.jp
+obama.nagasaki.jp
+omura.nagasaki.jp
+oseto.nagasaki.jp
+saikai.nagasaki.jp
+sasebo.nagasaki.jp
+seihi.nagasaki.jp
+shimabara.nagasaki.jp
+shinkamigoto.nagasaki.jp
+togitsu.nagasaki.jp
+tsushima.nagasaki.jp
+unzen.nagasaki.jp
+ando.nara.jp
+gose.nara.jp
+heguri.nara.jp
+higashiyoshino.nara.jp
+ikaruga.nara.jp
+ikoma.nara.jp
+kamikitayama.nara.jp
+kanmaki.nara.jp
+kashiba.nara.jp
+kashihara.nara.jp
+katsuragi.nara.jp
+kawai.nara.jp
+kawakami.nara.jp
+kawanishi.nara.jp
+koryo.nara.jp
+kurotaki.nara.jp
+mitsue.nara.jp
+miyake.nara.jp
+nara.nara.jp
+nosegawa.nara.jp
+oji.nara.jp
+ouda.nara.jp
+oyodo.nara.jp
+sakurai.nara.jp
+sango.nara.jp
+shimoichi.nara.jp
+shimokitayama.nara.jp
+shinjo.nara.jp
+soni.nara.jp
+takatori.nara.jp
+tawaramoto.nara.jp
+tenkawa.nara.jp
+tenri.nara.jp
+uda.nara.jp
+yamatokoriyama.nara.jp
+yamatotakada.nara.jp
+yamazoe.nara.jp
+yoshino.nara.jp
+aga.niigata.jp
+agano.niigata.jp
+gosen.niigata.jp
+itoigawa.niigata.jp
+izumozaki.niigata.jp
+joetsu.niigata.jp
+kamo.niigata.jp
+kariwa.niigata.jp
+kashiwazaki.niigata.jp
+minamiuonuma.niigata.jp
+mitsuke.niigata.jp
+muika.niigata.jp
+murakami.niigata.jp
+myoko.niigata.jp
+nagaoka.niigata.jp
+niigata.niigata.jp
+ojiya.niigata.jp
+omi.niigata.jp
+sado.niigata.jp
+sanjo.niigata.jp
+seiro.niigata.jp
+seirou.niigata.jp
+sekikawa.niigata.jp
+shibata.niigata.jp
+tagami.niigata.jp
+tainai.niigata.jp
+tochio.niigata.jp
+tokamachi.niigata.jp
+tsubame.niigata.jp
+tsunan.niigata.jp
+uonuma.niigata.jp
+yahiko.niigata.jp
+yoita.niigata.jp
+yuzawa.niigata.jp
+beppu.oita.jp
+bungoono.oita.jp
+bungotakada.oita.jp
+hasama.oita.jp
+hiji.oita.jp
+himeshima.oita.jp
+hita.oita.jp
+kamitsue.oita.jp
+kokonoe.oita.jp
+kuju.oita.jp
+kunisaki.oita.jp
+kusu.oita.jp
+oita.oita.jp
+saiki.oita.jp
+taketa.oita.jp
+tsukumi.oita.jp
+usa.oita.jp
+usuki.oita.jp
+yufu.oita.jp
+akaiwa.okayama.jp
+asakuchi.okayama.jp
+bizen.okayama.jp
+hayashima.okayama.jp
+ibara.okayama.jp
+kagamino.okayama.jp
+kasaoka.okayama.jp
+kibichuo.okayama.jp
+kumenan.okayama.jp
+kurashiki.okayama.jp
+maniwa.okayama.jp
+misaki.okayama.jp
+nagi.okayama.jp
+niimi.okayama.jp
+nishiawakura.okayama.jp
+okayama.okayama.jp
+satosho.okayama.jp
+setouchi.okayama.jp
+shinjo.okayama.jp
+shoo.okayama.jp
+soja.okayama.jp
+takahashi.okayama.jp
+tamano.okayama.jp
+tsuyama.okayama.jp
+wake.okayama.jp
+yakage.okayama.jp
+aguni.okinawa.jp
+ginowan.okinawa.jp
+ginoza.okinawa.jp
+gushikami.okinawa.jp
+haebaru.okinawa.jp
+higashi.okinawa.jp
+hirara.okinawa.jp
+iheya.okinawa.jp
+ishigaki.okinawa.jp
+ishikawa.okinawa.jp
+itoman.okinawa.jp
+izena.okinawa.jp
+kadena.okinawa.jp
+kin.okinawa.jp
+kitadaito.okinawa.jp
+kitanakagusuku.okinawa.jp
+kumejima.okinawa.jp
+kunigami.okinawa.jp
+minamidaito.okinawa.jp
+motobu.okinawa.jp
+nago.okinawa.jp
+naha.okinawa.jp
+nakagusuku.okinawa.jp
+nakijin.okinawa.jp
+nanjo.okinawa.jp
+nishihara.okinawa.jp
+ogimi.okinawa.jp
+okinawa.okinawa.jp
+onna.okinawa.jp
+shimoji.okinawa.jp
+taketomi.okinawa.jp
+tarama.okinawa.jp
+tokashiki.okinawa.jp
+tomigusuku.okinawa.jp
+tonaki.okinawa.jp
+urasoe.okinawa.jp
+uruma.okinawa.jp
+yaese.okinawa.jp
+yomitan.okinawa.jp
+yonabaru.okinawa.jp
+yonaguni.okinawa.jp
+zamami.okinawa.jp
+abeno.osaka.jp
+chihayaakasaka.osaka.jp
+chuo.osaka.jp
+daito.osaka.jp
+fujiidera.osaka.jp
+habikino.osaka.jp
+hannan.osaka.jp
+higashiosaka.osaka.jp
+higashisumiyoshi.osaka.jp
+higashiyodogawa.osaka.jp
+hirakata.osaka.jp
+ibaraki.osaka.jp
+ikeda.osaka.jp
+izumi.osaka.jp
+izumiotsu.osaka.jp
+izumisano.osaka.jp
+kadoma.osaka.jp
+kaizuka.osaka.jp
+kanan.osaka.jp
+kashiwara.osaka.jp
+katano.osaka.jp
+kawachinagano.osaka.jp
+kishiwada.osaka.jp
+kita.osaka.jp
+kumatori.osaka.jp
+matsubara.osaka.jp
+minato.osaka.jp
+minoh.osaka.jp
+misaki.osaka.jp
+moriguchi.osaka.jp
+neyagawa.osaka.jp
+nishi.osaka.jp
+nose.osaka.jp
+osakasayama.osaka.jp
+sakai.osaka.jp
+sayama.osaka.jp
+sennan.osaka.jp
+settsu.osaka.jp
+shijonawate.osaka.jp
+shimamoto.osaka.jp
+suita.osaka.jp
+tadaoka.osaka.jp
+taishi.osaka.jp
+tajiri.osaka.jp
+takaishi.osaka.jp
+takatsuki.osaka.jp
+tondabayashi.osaka.jp
+toyonaka.osaka.jp
+toyono.osaka.jp
+yao.osaka.jp
+ariake.saga.jp
+arita.saga.jp
+fukudomi.saga.jp
+genkai.saga.jp
+hamatama.saga.jp
+hizen.saga.jp
+imari.saga.jp
+kamimine.saga.jp
+kanzaki.saga.jp
+karatsu.saga.jp
+kashima.saga.jp
+kitagata.saga.jp
+kitahata.saga.jp
+kiyama.saga.jp
+kouhoku.saga.jp
+kyuragi.saga.jp
+nishiarita.saga.jp
+ogi.saga.jp
+omachi.saga.jp
+ouchi.saga.jp
+saga.saga.jp
+shiroishi.saga.jp
+taku.saga.jp
+tara.saga.jp
+tosu.saga.jp
+yoshinogari.saga.jp
+arakawa.saitama.jp
+asaka.saitama.jp
+chichibu.saitama.jp
+fujimi.saitama.jp
+fujimino.saitama.jp
+fukaya.saitama.jp
+hanno.saitama.jp
+hanyu.saitama.jp
+hasuda.saitama.jp
+hatogaya.saitama.jp
+hatoyama.saitama.jp
+hidaka.saitama.jp
+higashichichibu.saitama.jp
+higashimatsuyama.saitama.jp
+honjo.saitama.jp
+ina.saitama.jp
+iruma.saitama.jp
+iwatsuki.saitama.jp
+kamiizumi.saitama.jp
+kamikawa.saitama.jp
+kamisato.saitama.jp
+kasukabe.saitama.jp
+kawagoe.saitama.jp
+kawaguchi.saitama.jp
+kawajima.saitama.jp
+kazo.saitama.jp
+kitamoto.saitama.jp
+koshigaya.saitama.jp
+kounosu.saitama.jp
+kuki.saitama.jp
+kumagaya.saitama.jp
+matsubushi.saitama.jp
+minano.saitama.jp
+misato.saitama.jp
+miyashiro.saitama.jp
+miyoshi.saitama.jp
+moroyama.saitama.jp
+nagatoro.saitama.jp
+namegawa.saitama.jp
+niiza.saitama.jp
+ogano.saitama.jp
+ogawa.saitama.jp
+ogose.saitama.jp
+okegawa.saitama.jp
+omiya.saitama.jp
+otaki.saitama.jp
+ranzan.saitama.jp
+ryokami.saitama.jp
+saitama.saitama.jp
+sakado.saitama.jp
+satte.saitama.jp
+sayama.saitama.jp
+shiki.saitama.jp
+shiraoka.saitama.jp
+soka.saitama.jp
+sugito.saitama.jp
+toda.saitama.jp
+tokigawa.saitama.jp
+tokorozawa.saitama.jp
+tsurugashima.saitama.jp
+urawa.saitama.jp
+warabi.saitama.jp
+yashio.saitama.jp
+yokoze.saitama.jp
+yono.saitama.jp
+yorii.saitama.jp
+yoshida.saitama.jp
+yoshikawa.saitama.jp
+yoshimi.saitama.jp
+aisho.shiga.jp
+gamo.shiga.jp
+higashiomi.shiga.jp
+hikone.shiga.jp
+koka.shiga.jp
+konan.shiga.jp
+kosei.shiga.jp
+koto.shiga.jp
+kusatsu.shiga.jp
+maibara.shiga.jp
+moriyama.shiga.jp
+nagahama.shiga.jp
+nishiazai.shiga.jp
+notogawa.shiga.jp
+omihachiman.shiga.jp
+otsu.shiga.jp
+ritto.shiga.jp
+ryuoh.shiga.jp
+takashima.shiga.jp
+takatsuki.shiga.jp
+torahime.shiga.jp
+toyosato.shiga.jp
+yasu.shiga.jp
+akagi.shimane.jp
+ama.shimane.jp
+gotsu.shimane.jp
+hamada.shimane.jp
+higashiizumo.shimane.jp
+hikawa.shimane.jp
+hikimi.shimane.jp
+izumo.shimane.jp
+kakinoki.shimane.jp
+masuda.shimane.jp
+matsue.shimane.jp
+misato.shimane.jp
+nishinoshima.shimane.jp
+ohda.shimane.jp
+okinoshima.shimane.jp
+okuizumo.shimane.jp
+shimane.shimane.jp
+tamayu.shimane.jp
+tsuwano.shimane.jp
+unnan.shimane.jp
+yakumo.shimane.jp
+yasugi.shimane.jp
+yatsuka.shimane.jp
+arai.shizuoka.jp
+atami.shizuoka.jp
+fuji.shizuoka.jp
+fujieda.shizuoka.jp
+fujikawa.shizuoka.jp
+fujinomiya.shizuoka.jp
+fukuroi.shizuoka.jp
+gotemba.shizuoka.jp
+haibara.shizuoka.jp
+hamamatsu.shizuoka.jp
+higashiizu.shizuoka.jp
+ito.shizuoka.jp
+iwata.shizuoka.jp
+izu.shizuoka.jp
+izunokuni.shizuoka.jp
+kakegawa.shizuoka.jp
+kannami.shizuoka.jp
+kawanehon.shizuoka.jp
+kawazu.shizuoka.jp
+kikugawa.shizuoka.jp
+kosai.shizuoka.jp
+makinohara.shizuoka.jp
+matsuzaki.shizuoka.jp
+minamiizu.shizuoka.jp
+mishima.shizuoka.jp
+morimachi.shizuoka.jp
+nishiizu.shizuoka.jp
+numazu.shizuoka.jp
+omaezaki.shizuoka.jp
+shimada.shizuoka.jp
+shimizu.shizuoka.jp
+shimoda.shizuoka.jp
+shizuoka.shizuoka.jp
+susono.shizuoka.jp
+yaizu.shizuoka.jp
+yoshida.shizuoka.jp
+ashikaga.tochigi.jp
+bato.tochigi.jp
+haga.tochigi.jp
+ichikai.tochigi.jp
+iwafune.tochigi.jp
+kaminokawa.tochigi.jp
+kanuma.tochigi.jp
+karasuyama.tochigi.jp
+kuroiso.tochigi.jp
+mashiko.tochigi.jp
+mibu.tochigi.jp
+moka.tochigi.jp
+motegi.tochigi.jp
+nasu.tochigi.jp
+nasushiobara.tochigi.jp
+nikko.tochigi.jp
+nishikata.tochigi.jp
+nogi.tochigi.jp
+ohira.tochigi.jp
+ohtawara.tochigi.jp
+oyama.tochigi.jp
+sakura.tochigi.jp
+sano.tochigi.jp
+shimotsuke.tochigi.jp
+shioya.tochigi.jp
+takanezawa.tochigi.jp
+tochigi.tochigi.jp
+tsuga.tochigi.jp
+ujiie.tochigi.jp
+utsunomiya.tochigi.jp
+yaita.tochigi.jp
+aizumi.tokushima.jp
+anan.tokushima.jp
+ichiba.tokushima.jp
+itano.tokushima.jp
+kainan.tokushima.jp
+komatsushima.tokushima.jp
+matsushige.tokushima.jp
+mima.tokushima.jp
+minami.tokushima.jp
+miyoshi.tokushima.jp
+mugi.tokushima.jp
+nakagawa.tokushima.jp
+naruto.tokushima.jp
+sanagochi.tokushima.jp
+shishikui.tokushima.jp
+tokushima.tokushima.jp
+wajiki.tokushima.jp
+adachi.tokyo.jp
+akiruno.tokyo.jp
+akishima.tokyo.jp
+aogashima.tokyo.jp
+arakawa.tokyo.jp
+bunkyo.tokyo.jp
+chiyoda.tokyo.jp
+chofu.tokyo.jp
+chuo.tokyo.jp
+edogawa.tokyo.jp
+fuchu.tokyo.jp
+fussa.tokyo.jp
+hachijo.tokyo.jp
+hachioji.tokyo.jp
+hamura.tokyo.jp
+higashikurume.tokyo.jp
+higashimurayama.tokyo.jp
+higashiyamato.tokyo.jp
+hino.tokyo.jp
+hinode.tokyo.jp
+hinohara.tokyo.jp
+inagi.tokyo.jp
+itabashi.tokyo.jp
+katsushika.tokyo.jp
+kita.tokyo.jp
+kiyose.tokyo.jp
+kodaira.tokyo.jp
+koganei.tokyo.jp
+kokubunji.tokyo.jp
+komae.tokyo.jp
+koto.tokyo.jp
+kouzushima.tokyo.jp
+kunitachi.tokyo.jp
+machida.tokyo.jp
+meguro.tokyo.jp
+minato.tokyo.jp
+mitaka.tokyo.jp
+mizuho.tokyo.jp
+musashimurayama.tokyo.jp
+musashino.tokyo.jp
+nakano.tokyo.jp
+nerima.tokyo.jp
+ogasawara.tokyo.jp
+okutama.tokyo.jp
+ome.tokyo.jp
+oshima.tokyo.jp
+ota.tokyo.jp
+setagaya.tokyo.jp
+shibuya.tokyo.jp
+shinagawa.tokyo.jp
+shinjuku.tokyo.jp
+suginami.tokyo.jp
+sumida.tokyo.jp
+tachikawa.tokyo.jp
+taito.tokyo.jp
+tama.tokyo.jp
+toshima.tokyo.jp
+chizu.tottori.jp
+hino.tottori.jp
+kawahara.tottori.jp
+koge.tottori.jp
+kotoura.tottori.jp
+misasa.tottori.jp
+nanbu.tottori.jp
+nichinan.tottori.jp
+sakaiminato.tottori.jp
+tottori.tottori.jp
+wakasa.tottori.jp
+yazu.tottori.jp
+yonago.tottori.jp
+asahi.toyama.jp
+fuchu.toyama.jp
+fukumitsu.toyama.jp
+funahashi.toyama.jp
+himi.toyama.jp
+imizu.toyama.jp
+inami.toyama.jp
+johana.toyama.jp
+kamiichi.toyama.jp
+kurobe.toyama.jp
+nakaniikawa.toyama.jp
+namerikawa.toyama.jp
+nanto.toyama.jp
+nyuzen.toyama.jp
+oyabe.toyama.jp
+taira.toyama.jp
+takaoka.toyama.jp
+tateyama.toyama.jp
+toga.toyama.jp
+tonami.toyama.jp
+toyama.toyama.jp
+unazuki.toyama.jp
+uozu.toyama.jp
+yamada.toyama.jp
+arida.wakayama.jp
+aridagawa.wakayama.jp
+gobo.wakayama.jp
+hashimoto.wakayama.jp
+hidaka.wakayama.jp
+hirogawa.wakayama.jp
+inami.wakayama.jp
+iwade.wakayama.jp
+kainan.wakayama.jp
+kamitonda.wakayama.jp
+katsuragi.wakayama.jp
+kimino.wakayama.jp
+kinokawa.wakayama.jp
+kitayama.wakayama.jp
+koya.wakayama.jp
+koza.wakayama.jp
+kozagawa.wakayama.jp
+kudoyama.wakayama.jp
+kushimoto.wakayama.jp
+mihama.wakayama.jp
+misato.wakayama.jp
+nachikatsuura.wakayama.jp
+shingu.wakayama.jp
+shirahama.wakayama.jp
+taiji.wakayama.jp
+tanabe.wakayama.jp
+wakayama.wakayama.jp
+yuasa.wakayama.jp
+yura.wakayama.jp
+asahi.yamagata.jp
+funagata.yamagata.jp
+higashine.yamagata.jp
+iide.yamagata.jp
+kahoku.yamagata.jp
+kaminoyama.yamagata.jp
+kaneyama.yamagata.jp
+kawanishi.yamagata.jp
+mamurogawa.yamagata.jp
+mikawa.yamagata.jp
+murayama.yamagata.jp
+nagai.yamagata.jp
+nakayama.yamagata.jp
+nanyo.yamagata.jp
+nishikawa.yamagata.jp
+obanazawa.yamagata.jp
+oe.yamagata.jp
+oguni.yamagata.jp
+ohkura.yamagata.jp
+oishida.yamagata.jp
+sagae.yamagata.jp
+sakata.yamagata.jp
+sakegawa.yamagata.jp
+shinjo.yamagata.jp
+shirataka.yamagata.jp
+shonai.yamagata.jp
+takahata.yamagata.jp
+tendo.yamagata.jp
+tozawa.yamagata.jp
+tsuruoka.yamagata.jp
+yamagata.yamagata.jp
+yamanobe.yamagata.jp
+yonezawa.yamagata.jp
+yuza.yamagata.jp
+abu.yamaguchi.jp
+hagi.yamaguchi.jp
+hikari.yamaguchi.jp
+hofu.yamaguchi.jp
+iwakuni.yamaguchi.jp
+kudamatsu.yamaguchi.jp
+mitou.yamaguchi.jp
+nagato.yamaguchi.jp
+oshima.yamaguchi.jp
+shimonoseki.yamaguchi.jp
+shunan.yamaguchi.jp
+tabuse.yamaguchi.jp
+tokuyama.yamaguchi.jp
+toyota.yamaguchi.jp
+ube.yamaguchi.jp
+yuu.yamaguchi.jp
+chuo.yamanashi.jp
+doshi.yamanashi.jp
+fuefuki.yamanashi.jp
+fujikawa.yamanashi.jp
+fujikawaguchiko.yamanashi.jp
+fujiyoshida.yamanashi.jp
+hayakawa.yamanashi.jp
+hokuto.yamanashi.jp
+ichikawamisato.yamanashi.jp
+kai.yamanashi.jp
+kofu.yamanashi.jp
+koshu.yamanashi.jp
+kosuge.yamanashi.jp
+minami-alps.yamanashi.jp
+minobu.yamanashi.jp
+nakamichi.yamanashi.jp
+nanbu.yamanashi.jp
+narusawa.yamanashi.jp
+nirasaki.yamanashi.jp
+nishikatsura.yamanashi.jp
+oshino.yamanashi.jp
+otsuki.yamanashi.jp
+showa.yamanashi.jp
+tabayama.yamanashi.jp
+tsuru.yamanashi.jp
+uenohara.yamanashi.jp
+yamanakako.yamanashi.jp
+yamanashi.yamanashi.jp
+#ke
+co.ke
+or.ke
+ne.ke
+go.ke
+ac.ke
+sc.ke
+me.ke
+mobi.ke
+info.ke
+#kg
+org.kg
+net.kg
+com.kg
+edu.kg
+gov.kg
+mil.kg
+#kh
+per.kh
+com.kh
+edu.kh
+gov.kh
+mil.kh
+net.kh
+org.kh
+#ki
+edu.ki
+biz.ki
+net.ki
+org.ki
+gov.ki
+info.ki
+com.ki
+#km
+org.km
+nom.km
+gov.km
+prd.km
+tm.km
+edu.km
+mil.km
+ass.km
+com.km
+coop.km
+asso.km
+presse.km
+medecin.km
+notaires.km
+pharmaciens.km
+veterinaire.km
+gouv.km
+#kn
+net.kn
+org.kn
+edu.kn
+gov.kn
+com.kp
+edu.kp
+gov.kp
+org.kp
+rep.kp
+tra.kp
+#kr
+ac.kr
+co.kr
+es.kr
+go.kr
+hs.kr
+kg.kr
+mil.kr
+ms.kr
+ne.kr
+or.kr
+pe.kr
+re.kr
+sc.kr
+busan.kr
+chungbuk.kr
+chungnam.kr
+daegu.kr
+daejeon.kr
+gangwon.kr
+gwangju.kr
+gyeongbuk.kr
+gyeonggi.kr
+gyeongnam.kr
+incheon.kr
+jeju.kr
+jeonbuk.kr
+jeonnam.kr
+seoul.kr
+ulsan.kr
+#kw
+.com.kw
+.edu.kw
+.gov.kw
+.net.kw
+.org.kw
+.mil.kw
+#ky
+edu.ky
+gov.ky
+com.ky
+org.ky
+net.ky
+#kz
+org.kz
+edu.kz
+net.kz
+gov.kz
+mil.kz
+com.kz
+#la
+int.la
+net.la
+info.la
+edu.la
+gov.la
+per.la
+com.la
+org.la
+com.lb
+edu.lb
+gov.lb
+net.lb
+org.lb
+#lc
+com.lc
+net.lc
+co.lc
+org.lc
+edu.lc
+gov.lc
+#li
+#lk
+gov.lk
+sch.lk
+net.lk
+int.lk
+com.lk
+org.lk
+edu.lk
+ngo.lk
+soc.lk
+web.lk
+ltd.lk
+assn.lk
+grp.lk
+hotel.lk
+com.lr
+edu.lr
+gov.lr
+org.lr
+net.lr
+#ls
+co.ls
+org.ls
+#lt
+gov.lt
+#lu
+#lv
+com.lv
+edu.lv
+gov.lv
+org.lv
+mil.lv
+id.lv
+net.lv
+asn.lv
+conf.lv
+#ly
+com.ly
+net.ly
+gov.ly
+plc.ly
+edu.ly
+sch.ly
+med.ly
+org.ly
+id.ly
+#ma
+co.ma
+net.ma
+gov.ma
+org.ma
+ac.ma
+press.ma
+#mc
+tm.mc
+asso.mc
+#md
+#me
+co.me
+net.me
+org.me
+edu.me
+ac.me
+gov.me
+its.me
+priv.me
+#mg
+org.mg
+nom.mg
+gov.mg
+prd.mg
+tm.mg
+edu.mg
+mil.mg
+com.mg
+#mh
+#mil
+#mk
+com.mk
+org.mk
+net.mk
+edu.mk
+gov.mk
+inf.mk
+name.mk
+#ml
+com.ml
+edu.ml
+gouv.ml
+gov.ml
+net.ml
+org.ml
+presse.ml
+#mm
+net.mm
+com.mm
+edu.mm
+org.mm
+gov.mm
+#mn
+gov.mn
+edu.mn
+org.mn
+#mo
+com.mo
+net.mo
+org.mo
+edu.mo
+gov.mo
+#mobi
+#mp
+#mq
+#mr
+gov.mr
+#ms
+#mt
+mt
+org.mt
+com.mt
+gov.mt
+edu.mt
+net.mt
+#mu
+com.mu
+net.mu
+org.mu
+gov.mu
+ac.mu
+co.mu
+or.mu
+#museum
+academy.museum
+agriculture.museum
+air.museum
+airguard.museum
+alabama.museum
+alaska.museum
+amber.museum
+ambulance.museum
+american.museum
+americana.museum
+americanantiques.museum
+americanart.museum
+amsterdam.museum
+and.museum
+annefrank.museum
+anthro.museum
+anthropology.museum
+antiques.museum
+aquarium.museum
+arboretum.museum
+archaeological.museum
+archaeology.museum
+architecture.museum
+art.museum
+artanddesign.museum
+artcenter.museum
+artdeco.museum
+arteducation.museum
+artgallery.museum
+arts.museum
+artsandcrafts.museum
+asmatart.museum
+assassination.museum
+assisi.museum
+association.museum
+astronomy.museum
+atlanta.museum
+austin.museum
+australia.museum
+automotive.museum
+aviation.museum
+axis.museum
+badajoz.museum
+baghdad.museum
+bahn.museum
+bale.museum
+baltimore.museum
+barcelona.museum
+baseball.museum
+basel.museum
+baths.museum
+bauern.museum
+beauxarts.museum
+beeldengeluid.museum
+bellevue.museum
+bergbau.museum
+berkeley.museum
+berlin.museum
+bern.museum
+bible.museum
+bilbao.museum
+bill.museum
+birdart.museum
+birthplace.museum
+bonn.museum
+boston.museum
+botanical.museum
+botanicalgarden.museum
+botanicgarden.museum
+botany.museum
+brandywinevalley.museum
+brasil.museum
+bristol.museum
+british.museum
+britishcolumbia.museum
+broadcast.museum
+brunel.museum
+brussel.museum
+brussels.museum
+bruxelles.museum
+building.museum
+burghof.museum
+bus.museum
+bushey.museum
+cadaques.museum
+california.museum
+cambridge.museum
+can.museum
+canada.museum
+capebreton.museum
+carrier.museum
+cartoonart.museum
+casadelamoneda.museum
+castle.museum
+castres.museum
+celtic.museum
+center.museum
+chattanooga.museum
+cheltenham.museum
+chesapeakebay.museum
+chicago.museum
+children.museum
+childrens.museum
+childrensgarden.museum
+chiropractic.museum
+chocolate.museum
+christiansburg.museum
+cincinnati.museum
+cinema.museum
+circus.museum
+civilisation.museum
+civilization.museum
+civilwar.museum
+clinton.museum
+clock.museum
+coal.museum
+coastaldefence.museum
+cody.museum
+coldwar.museum
+collection.museum
+colonialwilliamsburg.museum
+coloradoplateau.museum
+columbia.museum
+columbus.museum
+communication.museum
+communications.museum
+community.museum
+computer.museum
+computerhistory.museum
+comunicacoes.museum
+contemporary.museum
+contemporaryart.museum
+convent.museum
+copenhagen.museum
+corporation.museum
+correios-e-telecomunicacoes.museum
+corvette.museum
+costume.museum
+countryestate.museum
+county.museum
+crafts.museum
+cranbrook.museum
+creation.museum
+cultural.museum
+culturalcenter.museum
+culture.museum
+cyber.museum
+cymru.museum
+dali.museum
+dallas.museum
+database.museum
+ddr.museum
+decorativearts.museum
+delaware.museum
+delmenhorst.museum
+denmark.museum
+depot.museum
+design.museum
+detroit.museum
+dinosaur.museum
+discovery.museum
+dolls.museum
+donostia.museum
+durham.museum
+eastafrica.museum
+eastcoast.museum
+education.museum
+educational.museum
+egyptian.museum
+eisenbahn.museum
+elburg.museum
+elvendrell.museum
+embroidery.museum
+encyclopedic.museum
+england.museum
+entomology.museum
+environment.museum
+environmentalconservation.museum
+epilepsy.museum
+essex.museum
+estate.museum
+ethnology.museum
+exeter.museum
+exhibition.museum
+family.museum
+farm.museum
+farmequipment.museum
+farmers.museum
+farmstead.museum
+field.museum
+figueres.museum
+filatelia.museum
+film.museum
+fineart.museum
+finearts.museum
+finland.museum
+flanders.museum
+florida.museum
+force.museum
+fortmissoula.museum
+fortworth.museum
+foundation.museum
+francaise.museum
+frankfurt.museum
+franziskaner.museum
+freemasonry.museum
+freiburg.museum
+fribourg.museum
+frog.museum
+fundacio.museum
+furniture.museum
+gallery.museum
+garden.museum
+gateway.museum
+geelvinck.museum
+gemological.museum
+geology.museum
+georgia.museum
+giessen.museum
+glas.museum
+glass.museum
+gorge.museum
+grandrapids.museum
+graz.museum
+guernsey.museum
+halloffame.museum
+hamburg.museum
+handson.museum
+harvestcelebration.museum
+hawaii.museum
+health.museum
+heimatunduhren.museum
+hellas.museum
+helsinki.museum
+hembygdsforbund.museum
+heritage.museum
+histoire.museum
+historical.museum
+historicalsociety.museum
+historichouses.museum
+historisch.museum
+historisches.museum
+history.museum
+historyofscience.museum
+horology.museum
+house.museum
+humanities.museum
+illustration.museum
+imageandsound.museum
+indian.museum
+indiana.museum
+indianapolis.museum
+indianmarket.museum
+intelligence.museum
+interactive.museum
+iraq.museum
+iron.museum
+isleofman.museum
+jamison.museum
+jefferson.museum
+jerusalem.museum
+jewelry.museum
+jewish.museum
+jewishart.museum
+jfk.museum
+journalism.museum
+judaica.museum
+judygarland.museum
+juedisches.museum
+juif.museum
+karate.museum
+karikatur.museum
+kids.museum
+koebenhavn.museum
+koeln.museum
+kunst.museum
+kunstsammlung.museum
+kunstunddesign.museum
+labor.museum
+labour.museum
+lajolla.museum
+lancashire.museum
+landes.museum
+lans.museum
+lans.museum
+larsson.museum
+lewismiller.museum
+lincoln.museum
+linz.museum
+living.museum
+livinghistory.museum
+localhistory.museum
+london.museum
+losangeles.museum
+louvre.museum
+loyalist.museum
+lucerne.museum
+luxembourg.museum
+luzern.museum
+mad.museum
+madrid.museum
+mallorca.museum
+manchester.museum
+mansion.museum
+mansions.museum
+manx.museum
+marburg.museum
+maritime.museum
+maritimo.museum
+maryland.museum
+marylhurst.museum
+media.museum
+medical.museum
+medizinhistorisches.museum
+meeres.museum
+memorial.museum
+mesaverde.museum
+michigan.museum
+midatlantic.museum
+military.museum
+mill.museum
+miners.museum
+mining.museum
+minnesota.museum
+missile.museum
+missoula.museum
+modern.museum
+moma.museum
+money.museum
+monmouth.museum
+monticello.museum
+montreal.museum
+moscow.museum
+motorcycle.museum
+muenchen.museum
+muenster.museum
+mulhouse.museum
+muncie.museum
+museet.museum
+museumcenter.museum
+museumvereniging.museum
+music.museum
+national.museum
+nationalfirearms.museum
+nationalheritage.museum
+nativeamerican.museum
+naturalhistory.museum
+naturalhistorymuseum.museum
+naturalsciences.museum
+nature.museum
+naturhistorisches.museum
+natuurwetenschappen.museum
+naumburg.museum
+naval.museum
+nebraska.museum
+neues.museum
+newhampshire.museum
+newjersey.museum
+newmexico.museum
+newport.museum
+newspaper.museum
+newyork.museum
+niepce.museum
+norfolk.museum
+north.museum
+nrw.museum
+nuernberg.museum
+nuremberg.museum
+nyc.museum
+nyny.museum
+oceanographic.museum
+oceanographique.museum
+omaha.museum
+online.museum
+ontario.museum
+openair.museum
+oregon.museum
+oregontrail.museum
+otago.museum
+oxford.museum
+pacific.museum
+paderborn.museum
+palace.museum
+paleo.museum
+palmsprings.museum
+panama.museum
+paris.museum
+pasadena.museum
+pharmacy.museum
+philadelphia.museum
+philadelphiaarea.museum
+philately.museum
+phoenix.museum
+photography.museum
+pilots.museum
+pittsburgh.museum
+planetarium.museum
+plantation.museum
+plants.museum
+plaza.museum
+portal.museum
+portland.museum
+portlligat.museum
+posts-and-telecommunications.museum
+preservation.museum
+presidio.museum
+press.museum
+project.museum
+public.museum
+pubol.museum
+quebec.museum
+railroad.museum
+railway.museum
+research.museum
+resistance.museum
+riodejaneiro.museum
+rochester.museum
+rockart.museum
+roma.museum
+russia.museum
+saintlouis.museum
+salem.museum
+salvadordali.museum
+salzburg.museum
+sandiego.museum
+sanfrancisco.museum
+santabarbara.museum
+santacruz.museum
+santafe.museum
+saskatchewan.museum
+satx.museum
+savannahga.museum
+schlesisches.museum
+schoenbrunn.museum
+schokoladen.museum
+school.museum
+schweiz.museum
+science.museum
+scienceandhistory.museum
+scienceandindustry.museum
+sciencecenter.museum
+sciencecenters.museum
+science-fiction.museum
+sciencehistory.museum
+sciences.museum
+sciencesnaturelles.museum
+scotland.museum
+seaport.museum
+settlement.museum
+settlers.museum
+shell.museum
+sherbrooke.museum
+sibenik.museum
+silk.museum
+ski.museum
+skole.museum
+society.museum
+sologne.museum
+soundandvision.museum
+southcarolina.museum
+southwest.museum
+space.museum
+spy.museum
+square.museum
+stadt.museum
+stalbans.museum
+starnberg.museum
+state.museum
+stateofdelaware.museum
+station.museum
+steam.museum
+steiermark.museum
+stjohn.museum
+stockholm.museum
+stpetersburg.museum
+stuttgart.museum
+suisse.museum
+surgeonshall.museum
+surrey.museum
+svizzera.museum
+sweden.museum
+sydney.museum
+tank.museum
+tcm.museum
+technology.museum
+telekommunikation.museum
+television.museum
+texas.museum
+textile.museum
+theater.museum
+time.museum
+timekeeping.museum
+topology.museum
+torino.museum
+touch.museum
+town.museum
+transport.museum
+tree.museum
+trolley.museum
+trust.museum
+trustee.museum
+uhren.museum
+ulm.museum
+undersea.museum
+university.museum
+usa.museum
+usantiques.museum
+usarts.museum
+uscountryestate.museum
+usculture.museum
+usdecorativearts.museum
+usgarden.museum
+ushistory.museum
+ushuaia.museum
+uslivinghistory.museum
+utah.museum
+uvic.museum
+valley.museum
+vantaa.museum
+versailles.museum
+viking.museum
+village.museum
+virginia.museum
+virtual.museum
+virtuel.museum
+vlaanderen.museum
+volkenkunde.museum
+wales.museum
+wallonie.museum
+war.museum
+washingtondc.museum
+watchandclock.museum
+watch-and-clock.museum
+western.museum
+westfalen.museum
+whaling.museum
+wildlife.museum
+williamsburg.museum
+windmill.museum
+workshop.museum
+york.museum
+yorkshire.museum
+yosemite.museum
+youth.museum
+zoological.museum
+zoology.museum
+#mv
+aero.mv
+biz.mv
+com.mv
+coop.mv
+edu.mv
+gov.mv
+info.mv
+int.mv
+mil.mv
+museum.mv
+name.mv
+net.mv
+org.mv
+pro.mv
+#mw
+ac.mw
+biz.mw
+co.mw
+com.mw
+coop.mw
+edu.mw
+gov.mw
+int.mw
+museum.mw
+net.mw
+org.mw
+#mx
+com.mx
+org.mx
+gob.mx
+edu.mx
+net.mx
+#my
+com.my
+net.my
+org.my
+gov.my
+edu.my
+mil.my
+name.my
+#mz
+adv.mz
+ac.mz
+co.mz
+org.mz
+gov.mz
+edu.mz
+#na
+info.na
+pro.na
+name.na
+school.na
+or.na
+dr.na
+us.na
+mx.na
+ca.na
+in.na
+cc.na
+tv.na
+ws.na
+mobi.na
+co.na
+com.na
+org.na
+#name
+#nc
+asso.nc
+#ne
+#net
+#nf
+com.nf
+net.nf
+per.nf
+rec.nf
+web.nf
+arts.nf
+firm.nf
+info.nf
+other.nf
+store.nf
+ac.ng
+com.ng
+edu.ng
+gov.ng
+net.ng
+org.ng
+#ni
+gob.ni
+co.ni
+ac.ni
+org.ni
+nom.ni
+net.ni
+mil.ni
+#nl
+bv.nl
+#no
+fhs.no
+vgs.no
+fylkesbibl.no
+folkebibl.no
+museum.no
+idrett.no
+priv.no
+mil.no
+stat.no
+dep.no
+kommune.no
+herad.no
+aa.no
+ah.no
+bu.no
+fm.no
+hl.no
+hm.no
+jan-mayen.no
+mr.no
+nl.no
+nt.no
+of.no
+ol.no
+oslo.no
+rl.no
+sf.no
+st.no
+svalbard.no
+tm.no
+tr.no
+va.no
+vf.no
+gs.aa.no
+gs.ah.no
+gs.bu.no
+gs.fm.no
+gs.hl.no
+gs.hm.no
+gs.jan-mayen.no
+gs.mr.no
+gs.nl.no
+gs.nt.no
+gs.of.no
+gs.ol.no
+gs.oslo.no
+gs.rl.no
+gs.sf.no
+gs.st.no
+gs.svalbard.no
+gs.tm.no
+gs.tr.no
+gs.va.no
+gs.vf.no
+akrehamn.no
+akrehamn.no
+algard.no
+algard.no
+arna.no
+brumunddal.no
+bryne.no
+bronnoysund.no
+bronnoysund.no
+drobak.no
+drobak.no
+egersund.no
+fetsund.no
+floro.no
+floro.no
+fredrikstad.no
+hokksund.no
+honefoss.no
+honefoss.no
+jessheim.no
+jorpeland.no
+jorpeland.no
+kirkenes.no
+kopervik.no
+krokstadelva.no
+langevag.no
+langevag.no
+leirvik.no
+mjondalen.no
+mjondalen.no
+mo-i-rana.no
+mosjoen.no
+mosjoen.no
+nesoddtangen.no
+orkanger.no
+osoyro.no
+osoyro.no
+raholt.no
+raholt.no
+sandnessjoen.no
+sandnessjoen.no
+skedsmokorset.no
+slattum.no
+spjelkavik.no
+stathelle.no
+stavern.no
+stjordalshalsen.no
+stjordalshalsen.no
+tananger.no
+tranby.no
+vossevangen.no
+afjord.no
+afjord.no
+agdenes.no
+al.no
+al.no
+alesund.no
+alesund.no
+alstahaug.no
+alta.no
+alta.no
+alaheadju.no
+alaheadju.no
+alvdal.no
+amli.no
+amli.no
+amot.no
+amot.no
+andebu.no
+andoy.no
+andoy.no
+andasuolo.no
+ardal.no
+ardal.no
+aremark.no
+arendal.no
+as.no
+aseral.no
+aseral.no
+asker.no
+askim.no
+askvoll.no
+askoy.no
+askoy.no
+asnes.no
+asnes.no
+audnedaln.no
+aukra.no
+aure.no
+aurland.no
+aurskog-holand.no
+aurskog-holand.no
+austevoll.no
+austrheim.no
+averoy.no
+averoy.no
+balestrand.no
+ballangen.no
+balat.no
+balat.no
+balsfjord.no
+bahccavuotna.no
+bahccavuotna.no
+bamble.no
+bardu.no
+beardu.no
+beiarn.no
+bajddar.no
+bajddar.no
+baidar.no
+baidar.no
+berg.no
+bergen.no
+berlevag.no
+berlevag.no
+bearalvahki.no
+bearalvahki.no
+bindal.no
+birkenes.no
+bjarkoy.no
+bjarkoy.no
+bjerkreim.no
+bjugn.no
+bodo.no
+bodo.no
+badaddja.no
+badaddja.no
+budejju.no
+bokn.no
+bremanger.no
+bronnoy.no
+bronnoy.no
+bygland.no
+bykle.no
+barum.no
+bo.telemark.no
+bo.telemark.no
+bo.nordland.no
+bo.nordland.no
+bievat.no
+bievat.no
+bomlo.no
+bomlo.no
+batsfjord.no
+batsfjord.no
+bahcavuotna.no
+bahcavuotna.no
+dovre.no
+drammen.no
+drangedal.no
+dyroy.no
+dyroy.no
+donna.no
+donna.no
+eid.no
+eidfjord.no
+eidsberg.no
+eidskog.no
+eidsvoll.no
+eigersund.no
+elverum.no
+enebakk.no
+engerdal.no
+etne.no
+etnedal.no
+evenes.no
+evenassi.no
+evenassi.no
+evje-og-hornnes.no
+farsund.no
+fauske.no
+fuossko.no
+fuoisku.no
+fedje.no
+fet.no
+finnoy.no
+finnoy.no
+fitjar.no
+fjaler.no
+fjell.no
+flakstad.no
+flatanger.no
+flekkefjord.no
+flesberg.no
+flora.no
+fla.no
+fla.no
+folldal.no
+forsand.no
+fosnes.no
+frei.no
+frogn.no
+froland.no
+frosta.no
+frana.no
+froya.no
+froya.no
+fusa.no
+fyresdal.no
+forde.no
+forde.no
+gamvik.no
+gangaviika.no
+gaular.no
+gausdal.no
+gildeskal.no
+gildeskal.no
+giske.no
+gjemnes.no
+gjerdrum.no
+gjerstad.no
+gjesdal.no
+gjovik.no
+gjovik.no
+gloppen.no
+gol.no
+gran.no
+grane.no
+granvin.no
+gratangen.no
+grimstad.no
+grong.no
+kraanghke.no
+kraanghke.no
+grue.no
+gulen.no
+hadsel.no
+halden.no
+halsa.no
+hamar.no
+hamaroy.no
+habmer.no
+habmer.no
+hapmir.no
+hapmir.no
+hammerfest.no
+hammarfeasta.no
+hammarfeasta.no
+haram.no
+hareid.no
+harstad.no
+hasvik.no
+aknoluokta.no
+hattfjelldal.no
+aarborte.no
+haugesund.no
+hemne.no
+hemnes.no
+hemsedal.no
+heroy.more-og-romsdal.no
+heroy.more-og-romsdal.no
+heroy.nordland.no
+heroy.nordland.no
+hitra.no
+hjartdal.no
+hjelmeland.no
+hobol.no
+hobol.no
+hof.no
+hol.no
+hole.no
+holmestrand.no
+holtalen.no
+holtalen.no
+hornindal.no
+horten.no
+hurdal.no
+hurum.no
+hvaler.no
+hyllestad.no
+hagebostad.no
+hoyanger.no
+hoyanger.no
+hoylandet.no
+hoylandet.no
+ha.no
+ha.no
+ibestad.no
+inderoy.no
+inderoy.no
+iveland.no
+jevnaker.no
+jondal.no
+jolster.no
+jolster.no
+karasjok.no
+karasjohka.no
+karasjohka.no
+karlsoy.no
+galsa.no
+galsa.no
+karmoy.no
+karmoy.no
+kautokeino.no
+guovdageaidnu.no
+klepp.no
+klabu.no
+kongsberg.no
+kongsvinger.no
+kragero.no
+kragero.no
+kristiansand.no
+kristiansund.no
+krodsherad.no
+krodsherad.no
+kvalsund.no
+rahkkeravju.no
+rahkkeravju.no
+kvam.no
+kvinesdal.no
+kvinnherad.no
+kviteseid.no
+kvitsoy.no
+kvitsoy.no
+kvafjord.no
+giehtavuoatna.no
+kvanangen.no
+navuotna.no
+navuotna.no
+kafjord.no
+kafjord.no
+gaivuotna.no
+gaivuotna.no
+larvik.no
+lavangen.no
+lavagis.no
+loabat.no
+loabat.no
+lebesby.no
+davvesiida.no
+leikanger.no
+leirfjord.no
+leka.no
+leksvik.no
+lenvik.no
+leangaviika.no
+lesja.no
+levanger.no
+lier.no
+lierne.no
+lillehammer.no
+lillesand.no
+lindesnes.no
+lindas.no
+lindas.no
+lom.no
+loppa.no
+lahppi.no
+lahppi.no
+lund.no
+lunner.no
+luroy.no
+luroy.no
+luster.no
+lyngdal.no
+lyngen.no
+ivgu.no
+lardal.no
+lerdal.no
+lodingen.no
+lodingen.no
+lorenskog.no
+lorenskog.no
+loten.no
+loten.no
+malvik.no
+masoy.no
+masoy.no
+muosat.no
+muosat.no
+mandal.no
+marker.no
+marnardal.no
+masfjorden.no
+meland.no
+meldal.no
+melhus.no
+meloy.no
+meloy.no
+meraker.no
+meraker.no
+moareke.no
+moareke.no
+midsund.no
+midtre-gauldal.no
+modalen.no
+modum.no
+molde.no
+moskenes.no
+moss.no
+mosvik.no
+malselv.no
+malselv.no
+malatvuopmi.no
+malatvuopmi.no
+namdalseid.no
+aejrie.no
+namsos.no
+namsskogan.no
+naamesjevuemie.no
+naamesjevuemie.no
+laakesvuemie.no
+nannestad.no
+narvik.no
+narviika.no
+naustdal.no
+nedre-eiker.no
+nes.akershus.no
+nes.buskerud.no
+nesna.no
+nesodden.no
+nesseby.no
+unjarga.no
+unjarga.no
+nesset.no
+nissedal.no
+nittedal.no
+nord-aurdal.no
+nord-fron.no
+nord-odal.no
+norddal.no
+nordkapp.no
+davvenjarga.no
+davvenjarga.no
+nordre-land.no
+nordreisa.no
+raisa.no
+raisa.no
+nore-og-uvdal.no
+notodden.no
+naroy.no
+notteroy.no
+notteroy.no
+odda.no
+oksnes.no
+oksnes.no
+oppdal.no
+oppegard.no
+oppegard.no
+orkdal.no
+orland.no
+orland.no
+orskog.no
+orskog.no
+orsta.no
+orsta.no
+os.hedmark.no
+os.hordaland.no
+osen.no
+osteroy.no
+osteroy.no
+ostre-toten.no
+ostre-toten.no
+overhalla.no
+ovre-eiker.no
+ovre-eiker.no
+oyer.no
+oyer.no
+oygarden.no
+oygarden.no
+oystre-slidre.no
+oystre-slidre.no
+porsanger.no
+porsangu.no
+porsgrunn.no
+radoy.no
+radoy.no
+rakkestad.no
+rana.no
+ruovat.no
+randaberg.no
+rauma.no
+rendalen.no
+rennebu.no
+rennesoy.no
+rennesoy.no
+rindal.no
+ringebu.no
+ringerike.no
+ringsaker.no
+rissa.no
+risor.no
+risor.no
+roan.no
+rollag.no
+rygge.no
+ralingen.no
+rodoy.no
+rodoy.no
+romskog.no
+romskog.no
+roros.no
+roros.no
+rost.no
+rost.no
+royken.no
+royken.no
+royrvik.no
+royrvik.no
+rade.no
+rade.no
+salangen.no
+siellak.no
+saltdal.no
+salat.no
+salat.no
+salat.no
+samnanger.no
+sande.more-og-romsdal.no
+sande.more-og-romsdal.no
+sande.vestfold.no
+sandefjord.no
+sandnes.no
+sandoy.no
+sandoy.no
+sarpsborg.no
+sauda.no
+sauherad.no
+sel.no
+selbu.no
+selje.no
+seljord.no
+sigdal.no
+siljan.no
+sirdal.no
+skaun.no
+skedsmo.no
+ski.no
+skien.no
+skiptvet.no
+skjervoy.no
+skjervoy.no
+skierva.no
+skierva.no
+skjak.no
+skjak.no
+skodje.no
+skanland.no
+skanland.no
+skanit.no
+skanit.no
+smola.no
+smola.no
+snillfjord.no
+snasa.no
+snasa.no
+snoasa.no
+snaase.no
+snaase.no
+sogndal.no
+sokndal.no
+sola.no
+solund.no
+songdalen.no
+sortland.no
+spydeberg.no
+stange.no
+stavanger.no
+steigen.no
+steinkjer.no
+stjordal.no
+stjordal.no
+stokke.no
+stor-elvdal.no
+stord.no
+stordal.no
+storfjord.no
+omasvuotna.no
+strand.no
+stranda.no
+stryn.no
+sula.no
+suldal.no
+sund.no
+sunndal.no
+surnadal.no
+sveio.no
+svelvik.no
+sykkylven.no
+sogne.no
+sogne.no
+somna.no
+somna.no
+sondre-land.no
+sondre-land.no
+sor-aurdal.no
+sor-aurdal.no
+sor-fron.no
+sor-fron.no
+sor-odal.no
+sor-odal.no
+sor-varanger.no
+sor-varanger.no
+matta-varjjat.no
+matta-varjjat.no
+sorfold.no
+sorfold.no
+sorreisa.no
+sorreisa.no
+sorum.no
+sorum.no
+tana.no
+deatnu.no
+time.no
+tingvoll.no
+tinn.no
+tjeldsund.no
+dielddanuorri.no
+tjome.no
+tjome.no
+tokke.no
+tolga.no
+torsken.no
+tranoy.no
+tranoy.no
+tromso.no
+tromso.no
+tromsa.no
+romsa.no
+trondheim.no
+troandin.no
+trysil.no
+trana.no
+trogstad.no
+trogstad.no
+tvedestrand.no
+tydal.no
+tynset.no
+tysfjord.no
+divtasvuodna.no
+divttasvuotna.no
+tysnes.no
+tysvar.no
+tonsberg.no
+tonsberg.no
+ullensaker.no
+ullensvang.no
+ulvik.no
+utsira.no
+vadso.no
+vadso.no
+cahcesuolo.no
+cahcesuolo.no
+vaksdal.no
+valle.no
+vang.no
+vanylven.no
+vardo.no
+vardo.no
+varggat.no
+varggat.no
+vefsn.no
+vaapste.no
+vega.no
+vegarshei.no
+vegarshei.no
+vennesla.no
+verdal.no
+verran.no
+vestby.no
+vestnes.no
+vestre-slidre.no
+vestre-toten.no
+vestvagoy.no
+vestvagoy.no
+vevelstad.no
+vik.no
+vikna.no
+vindafjord.no
+volda.no
+voss.no
+varoy.no
+vagan.no
+vagan.no
+voagat.no
+vagsoy.no
+vagsoy.no
+vaga.no
+vaga.no
+valer.ostfold.no
+valer.ostfold.no
+valer.hedmark.no
+valer.hedmark.no
+#np
+com.np
+edu.np
+gov.np
+mil.np
+net.np
+org.np
+name.np
+pro.np
+info.np
+#nr
+biz.nr
+info.nr
+gov.nr
+edu.nr
+org.nr
+net.nr
+com.nr
+#nu
+#nz
+ac.nz
+co.nz
+geek.nz
+gen.nz
+kiwi.nz
+maori.nz
+net.nz
+org.nz
+school.nz
+cri.nz
+govt.nz
+iwi.nz
+parliament.nz
+mil.nz
+health.nz
+#om
+om
+co.om
+com.om
+org.om
+net.om
+edu.om
+gov.om
+museum.om
+pro.om
+med.om
+#org
+#pa
+ac.pa
+gob.pa
+com.pa
+org.pa
+sld.pa
+edu.pa
+net.pa
+ing.pa
+abo.pa
+med.pa
+nom.pa
+#pe
+edu.pe
+gob.pe
+nom.pe
+mil.pe
+org.pe
+com.pe
+net.pe
+#pf
+com.pf
+org.pf
+edu.pf
+#pg
+com.pg
+net.pg
+ac.pg
+gov.pg
+mil.pg
+org.pg.
+#ph
+com.ph
+net.ph
+org.ph
+gov.ph
+edu.ph
+ngo.ph
+mil.ph
+i.ph
+#pk
+com.pk
+net.pk
+edu.pk
+org.pk
+fam.pk
+biz.pk
+web.pk
+gov.pk
+gob.pk
+gok.pk
+gon.pk
+gop.pk
+gos.pk
+info.pk
+#pl
+aid.pl
+agro.pl
+atm.pl
+auto.pl
+biz.pl
+com.pl
+edu.pl
+gmina.pl
+gsm.pl
+info.pl
+mail.pl
+miasta.pl
+media.pl
+mil.pl
+net.pl
+nieruchomosci.pl
+nom.pl
+org.pl
+pc.pl
+powiat.pl
+priv.pl
+realestate.pl
+rel.pl
+sex.pl
+shop.pl
+sklep.pl
+sos.pl
+szkola.pl
+targi.pl
+tm.pl
+tourism.pl
+travel.pl
+turystyka.pl
+6bone.pl
+art.pl
+mbone.pl
+gov.pl
+uw.gov.pl
+um.gov.pl
+ug.gov.pl
+upow.gov.pl
+starostwo.gov.pl
+so.gov.pl
+sr.gov.pl
+po.gov.pl
+pa.gov.pl
+ngo.pl
+irc.pl
+usenet.pl
+augustow.pl
+babia-gora.pl
+bedzin.pl
+beskidy.pl
+bialowieza.pl
+bialystok.pl
+bielawa.pl
+bieszczady.pl
+boleslawiec.pl
+bydgoszcz.pl
+bytom.pl
+cieszyn.pl
+czeladz.pl
+czest.pl
+dlugoleka.pl
+elblag.pl
+elk.pl
+glogow.pl
+gniezno.pl
+gorlice.pl
+grajewo.pl
+ilawa.pl
+jaworzno.pl
+jelenia-gora.pl
+jgora.pl
+kalisz.pl
+kazimierz-dolny.pl
+karpacz.pl
+kartuzy.pl
+kaszuby.pl
+katowice.pl
+kepno.pl
+ketrzyn.pl
+klodzko.pl
+kobierzyce.pl
+kolobrzeg.pl
+konin.pl
+konskowola.pl
+kutno.pl
+lapy.pl
+lebork.pl
+legnica.pl
+lezajsk.pl
+limanowa.pl
+lomza.pl
+lowicz.pl
+lubin.pl
+lukow.pl
+malbork.pl
+malopolska.pl
+mazowsze.pl
+mazury.pl
+mielec.pl
+mielno.pl
+mragowo.pl
+naklo.pl
+nowaruda.pl
+nysa.pl
+olawa.pl
+olecko.pl
+olkusz.pl
+olsztyn.pl
+opoczno.pl
+opole.pl
+ostroda.pl
+ostroleka.pl
+ostrowiec.pl
+ostrowwlkp.pl
+pila.pl
+pisz.pl
+podhale.pl
+podlasie.pl
+polkowice.pl
+pomorze.pl
+pomorskie.pl
+prochowice.pl
+pruszkow.pl
+przeworsk.pl
+pulawy.pl
+radom.pl
+rawa-maz.pl
+rybnik.pl
+rzeszow.pl
+sanok.pl
+sejny.pl
+siedlce.pl
+slask.pl
+slupsk.pl
+sosnowiec.pl
+stalowa-wola.pl
+skoczow.pl
+starachowice.pl
+stargard.pl
+suwalki.pl
+swidnica.pl
+swiebodzin.pl
+swinoujscie.pl
+szczecin.pl
+szczytno.pl
+tarnobrzeg.pl
+tgory.pl
+turek.pl
+tychy.pl
+ustka.pl
+walbrzych.pl
+warmia.pl
+warszawa.pl
+waw.pl
+wegrow.pl
+wielun.pl
+wlocl.pl
+wloclawek.pl
+wodzislaw.pl
+wolomin.pl
+wroclaw.pl
+zachpomor.pl
+zagan.pl
+zarow.pl
+zgora.pl
+zgorzelec.pl
+gda.pl
+gdansk.pl
+gdynia.pl
+med.pl
+sopot.pl
+gliwice.pl
+krakow.pl
+poznan.pl
+wroc.pl
+zakopane.pl
+#pm
+#pn
+gov.pn
+co.pn
+org.pn
+edu.pn
+net.pn
+#post
+#pr
+com.pr
+net.pr
+org.pr
+gov.pr
+edu.pr
+isla.pr
+pro.pr
+biz.pr
+info.pr
+name.pr
+est.pr
+prof.pr
+ac.pr
+#pro
+aca.pro
+bar.pro
+cpa.pro
+jur.pro
+law.pro
+med.pro
+eng.pro
+#ps
+edu.ps
+gov.ps
+sec.ps
+plo.ps
+com.ps
+org.ps
+net.ps
+#pt
+net.pt
+gov.pt
+org.pt
+edu.pt
+int.pt
+publ.pt
+com.pt
+nome.pt
+#pw
+co.pw
+ne.pw
+or.pw
+ed.pw
+go.pw
+belau.pw
+#py
+com.py
+coop.py
+edu.py
+gov.py
+mil.py
+net.py
+org.py
+#qa
+com.qa
+edu.qa
+gov.qa
+mil.qa
+name.qa
+net.qa
+org.qa
+sch.qa
+#re
+com.re
+asso.re
+nom.re
+#ro
+com.ro
+org.ro
+tm.ro
+nt.ro
+nom.ro
+info.ro
+rec.ro
+arts.ro
+firm.ro
+store.ro
+www.ro
+#rs
+co.rs
+org.rs
+edu.rs
+ac.rs
+gov.rs
+in.rs
+#ru
+ac.ru
+com.ru
+edu.ru
+int.ru
+net.ru
+org.ru
+pp.ru
+adygeya.ru
+altai.ru
+amur.ru
+arkhangelsk.ru
+astrakhan.ru
+bashkiria.ru
+belgorod.ru
+bir.ru
+bryansk.ru
+buryatia.ru
+cbg.ru
+chel.ru
+chelyabinsk.ru
+chita.ru
+chukotka.ru
+chuvashia.ru
+dagestan.ru
+dudinka.ru
+e-burg.ru
+grozny.ru
+irkutsk.ru
+ivanovo.ru
+izhevsk.ru
+jar.ru
+joshkar-ola.ru
+kalmykia.ru
+kaluga.ru
+kamchatka.ru
+karelia.ru
+kazan.ru
+kchr.ru
+kemerovo.ru
+khabarovsk.ru
+khakassia.ru
+khv.ru
+kirov.ru
+koenig.ru
+komi.ru
+kostroma.ru
+krasnoyarsk.ru
+kuban.ru
+kurgan.ru
+kursk.ru
+lipetsk.ru
+magadan.ru
+mari.ru
+mari-el.ru
+marine.ru
+mordovia.ru
+mosreg.ru
+msk.ru
+murmansk.ru
+nalchik.ru
+nnov.ru
+nov.ru
+novosibirsk.ru
+nsk.ru
+omsk.ru
+orenburg.ru
+oryol.ru
+palana.ru
+penza.ru
+perm.ru
+pskov.ru
+ptz.ru
+rnd.ru
+ryazan.ru
+sakhalin.ru
+samara.ru
+saratov.ru
+simbirsk.ru
+smolensk.ru
+spb.ru
+stavropol.ru
+stv.ru
+surgut.ru
+tambov.ru
+tatarstan.ru
+tom.ru
+tomsk.ru
+tsaritsyn.ru
+tsk.ru
+tula.ru
+tuva.ru
+tver.ru
+tyumen.ru
+udm.ru
+udmurtia.ru
+ulan-ude.ru
+vladikavkaz.ru
+vladimir.ru
+vladivostok.ru
+volgograd.ru
+vologda.ru
+voronezh.ru
+vrn.ru
+vyatka.ru
+yakutia.ru
+yamal.ru
+yaroslavl.ru
+yekaterinburg.ru
+yuzhno-sakhalinsk.ru
+amursk.ru
+baikal.ru
+cmw.ru
+fareast.ru
+jamal.ru
+kms.ru
+k-uralsk.ru
+kustanai.ru
+kuzbass.ru
+magnitka.ru
+mytis.ru
+nakhodka.ru
+nkz.ru
+norilsk.ru
+oskol.ru
+pyatigorsk.ru
+rubtsovsk.ru
+snz.ru
+syzran.ru
+vdonsk.ru
+zgrad.ru
+gov.ru
+mil.ru
+test.ru
+#rw
+gov.rw
+net.rw
+edu.rw
+ac.rw
+com.rw
+co.rw
+int.rw
+mil.rw
+gouv.rw
+#sa
+com.sa
+net.sa
+org.sa
+gov.sa
+med.sa
+pub.sa
+edu.sa
+sch.sa
+#sb
+com.sb
+edu.sb
+gov.sb
+net.sb
+org.sb
+#sc
+com.sc
+gov.sc
+net.sc
+org.sc
+edu.sc
+#sd
+com.sd
+net.sd
+org.sd
+edu.sd
+med.sd
+tv.sd
+gov.sd
+info.sd
+#se
+a.se
+ac.se
+b.se
+bd.se
+brand.se
+c.se
+d.se
+e.se
+f.se
+fh.se
+fhsk.se
+fhv.se
+g.se
+h.se
+i.se
+k.se
+komforb.se
+kommunalforbund.se
+komvux.se
+l.se
+lanbib.se
+m.se
+n.se
+naturbruksgymn.se
+o.se
+org.se
+p.se
+parti.se
+pp.se
+press.se
+r.se
+s.se
+sshn.se
+t.se
+tm.se
+u.se
+w.se
+x.se
+y.se
+z.se
+#sg
+com.sg
+net.sg
+org.sg
+gov.sg
+edu.sg
+per.sg
+#sh
+com.sh
+net.sh
+gov.sh
+org.sh
+mil.sh
+#si
+#sk
+#sl
+com.sl
+net.sl
+edu.sl
+gov.sl
+org.sl
+#sm
+#sn
+art.sn
+com.sn
+edu.sn
+gouv.sn
+org.sn
+perso.sn
+univ.sn
+#so
+com.so
+net.so
+org.so
+#sr
+#st
+co.st
+com.st
+consulado.st
+edu.st
+embaixada.st
+gov.st
+mil.st
+net.st
+org.st
+principe.st
+saotome.st
+store.st
+#su
+#sv
+edu.sv
+gob.sv
+com.sv
+org.sv
+red.sv
+#sx
+gov.sx
+#sy
+edu.sy
+gov.sy
+net.sy
+mil.sy
+com.sy
+org.sy
+#sz
+co.sz
+ac.sz
+org.sz
+#tc
+#td
+#tel
+#tf
+#tg
+#th
+ac.th
+co.th
+go.th
+in.th
+mi.th
+net.th
+or.th
+#tj
+ac.tj
+biz.tj
+co.tj
+com.tj
+edu.tj
+go.tj
+gov.tj
+int.tj
+mil.tj
+name.tj
+net.tj
+nic.tj
+org.tj
+test.tj
+web.tj
+#tk
+#tl
+gov.tl
+#tm
+com.tm
+co.tm
+org.tm
+net.tm
+nom.tm
+gov.tm
+mil.tm
+edu.tm
+#tn
+com.tn
+ens.tn
+fin.tn
+gov.tn
+ind.tn
+intl.tn
+nat.tn
+net.tn
+org.tn
+info.tn
+perso.tn
+tourism.tn
+edunet.tn
+rnrt.tn
+rns.tn
+rnu.tn
+mincom.tn
+agrinet.tn
+defense.tn
+turen.tn
+#to
+com.to
+gov.to
+net.to
+org.to
+edu.to
+mil.to
+#tr
+com.tr
+info.tr
+biz.tr
+net.tr
+org.tr
+web.tr
+gen.tr
+av.tr
+dr.tr
+bbs.tr
+name.tr
+tel.tr
+gov.tr
+bel.tr
+pol.tr
+mil.tr
+k12.tr
+edu.tr
+#travel
+#tt
+co.tt
+com.tt
+org.tt
+net.tt
+biz.tt
+info.tt
+pro.tt
+int.tt
+coop.tt
+jobs.tt
+mobi.tt
+travel.tt
+museum.tt
+aero.tt
+name.tt
+gov.tt
+edu.tt
+#tv
+#tw
+edu.tw
+gov.tw
+mil.tw
+com.tw
+net.tw
+org.tw
+idv.tw
+game.tw
+ebiz.tw
+club.tw
+ac.tz
+co.tz
+go.tz
+hotel.tz
+info.tz
+me.tz
+mil.tz
+mobi.tz
+ne.tz
+or.tz
+sc.tz
+tv.tz
+#ua
+com.ua
+edu.ua
+gov.ua
+in.ua
+net.ua
+org.ua
+cherkassy.ua
+cherkasy.ua
+chernigov.ua
+chernihiv.ua
+chernivtsi.ua
+chernovtsy.ua
+ck.ua
+cn.ua
+cr.ua
+crimea.ua
+cv.ua
+dn.ua
+dnepropetrovsk.ua
+dnipropetrovsk.ua
+dominic.ua
+donetsk.ua
+dp.ua
+if.ua
+ivano-frankivsk.ua
+kh.ua
+kharkiv.ua
+kharkov.ua
+kherson.ua
+khmelnitskiy.ua
+khmelnytskyi.ua
+kiev.ua
+kirovograd.ua
+km.ua
+kr.ua
+krym.ua
+ks.ua
+kv.ua
+kyiv.ua
+lg.ua
+lt.ua
+lugansk.ua
+lutsk.ua
+lv.ua
+lviv.ua
+mk.ua
+mykolaiv.ua
+nikolaev.ua
+od.ua
+odesa.ua
+odessa.ua
+pl.ua
+poltava.ua
+rivne.ua
+rovno.ua
+rv.ua
+sb.ua
+sebastopol.ua
+sevastopol.ua
+sm.ua
+sumy.ua
+te.ua
+ternopil.ua
+uz.ua
+uzhgorod.ua
+vinnica.ua
+vinnytsia.ua
+vn.ua
+volyn.ua
+yalta.ua
+zaporizhzhe.ua
+zaporizhzhia.ua
+zhitomir.ua
+zhytomyr.ua
+zp.ua
+zt.ua
+co.ua
+pp.ua
+#ug
+co.ug
+or.ug
+ac.ug
+sc.ug
+go.ug
+ne.ug
+com.ug
+org.ug
+#uk
+ac.uk
+co.uk
+gov.uk
+judiciary.uk
+ltd.uk
+me.uk
+mod.uk
+net.uk
+nhs.uk
+nic.uk
+org.uk
+parliament.uk
+plc.uk
+police.uk
+sch.uk
+bl.uk
+jet.uk
+nls.uk
+#us
+dni.us
+fed.us
+isa.us
+kids.us
+nsn.us
+ak.us
+al.us
+ar.us
+as.us
+az.us
+ca.us
+co.us
+ct.us
+dc.us
+de.us
+fl.us
+ga.us
+gu.us
+hi.us
+ia.us
+id.us
+il.us
+in.us
+ks.us
+ky.us
+la.us
+ma.us
+md.us
+me.us
+mi.us
+mn.us
+mo.us
+ms.us
+mt.us
+nc.us
+nd.us
+ne.us
+nh.us
+nj.us
+nm.us
+nv.us
+ny.us
+oh.us
+ok.us
+or.us
+pa.us
+pr.us
+ri.us
+sc.us
+sd.us
+tn.us
+tx.us
+ut.us
+vi.us
+vt.us
+va.us
+wa.us
+wi.us
+wv.us
+wy.us
+k12.ak.us
+k12.al.us
+k12.ar.us
+k12.as.us
+k12.az.us
+k12.ca.us
+k12.co.us
+k12.ct.us
+k12.dc.us
+k12.de.us
+k12.fl.us
+k12.ga.us
+k12.gu.us
+k12.ia.us
+k12.id.us
+k12.il.us
+k12.in.us
+k12.ks.us
+k12.ky.us
+k12.la.us
+k12.ma.us
+k12.md.us
+k12.me.us
+k12.mi.us
+k12.mn.us
+k12.mo.us
+k12.ms.us
+k12.mt.us
+k12.nc.us
+k12.nd.us
+k12.ne.us
+k12.nh.us
+k12.nj.us
+k12.nm.us
+k12.nv.us
+k12.ny.us
+k12.oh.us
+k12.ok.us
+k12.or.us
+k12.pa.us
+k12.pr.us
+k12.ri.us
+k12.sc.us
+k12.sd.us
+k12.tn.us
+k12.tx.us
+k12.ut.us
+k12.vi.us
+k12.vt.us
+k12.va.us
+k12.wa.us
+k12.wi.us
+k12.wv.us
+k12.wy.us
+cc.ak.us
+cc.al.us
+cc.ar.us
+cc.as.us
+cc.az.us
+cc.ca.us
+cc.co.us
+cc.ct.us
+cc.dc.us
+cc.de.us
+cc.fl.us
+cc.ga.us
+cc.gu.us
+cc.hi.us
+cc.ia.us
+cc.id.us
+cc.il.us
+cc.in.us
+cc.ks.us
+cc.ky.us
+cc.la.us
+cc.ma.us
+cc.md.us
+cc.me.us
+cc.mi.us
+cc.mn.us
+cc.mo.us
+cc.ms.us
+cc.mt.us
+cc.nc.us
+cc.nd.us
+cc.ne.us
+cc.nh.us
+cc.nj.us
+cc.nm.us
+cc.nv.us
+cc.ny.us
+cc.oh.us
+cc.ok.us
+cc.or.us
+cc.pa.us
+cc.pr.us
+cc.ri.us
+cc.sc.us
+cc.sd.us
+cc.tn.us
+cc.tx.us
+cc.ut.us
+cc.vi.us
+cc.vt.us
+cc.va.us
+cc.wa.us
+cc.wi.us
+cc.wv.us
+cc.wy.us
+lib.ak.us
+lib.al.us
+lib.ar.us
+lib.as.us
+lib.az.us
+lib.ca.us
+lib.co.us
+lib.ct.us
+lib.dc.us
+lib.de.us
+lib.fl.us
+lib.ga.us
+lib.gu.us
+lib.hi.us
+lib.ia.us
+lib.id.us
+lib.il.us
+lib.in.us
+lib.ks.us
+lib.ky.us
+lib.la.us
+lib.ma.us
+lib.md.us
+lib.me.us
+lib.mi.us
+lib.mn.us
+lib.mo.us
+lib.ms.us
+lib.mt.us
+lib.nc.us
+lib.nd.us
+lib.ne.us
+lib.nh.us
+lib.nj.us
+lib.nm.us
+lib.nv.us
+lib.ny.us
+lib.oh.us
+lib.ok.us
+lib.or.us
+lib.pa.us
+lib.pr.us
+lib.ri.us
+lib.sc.us
+lib.sd.us
+lib.tn.us
+lib.tx.us
+lib.ut.us
+lib.vi.us
+lib.vt.us
+lib.va.us
+lib.wa.us
+lib.wi.us
+lib.wv.us
+lib.wy.us
+pvt.k12.ma.us
+chtr.k12.ma.us
+paroch.k12.ma.us
+#uy
+com.uy
+edu.uy
+gub.uy
+mil.uy
+net.uy
+org.uy
+#uz
+co.uz
+com.uz
+net.uz
+org.uz
+#va
+#vc
+com.vc
+net.vc
+org.vc
+gov.vc
+mil.vc
+edu.vc
+#ve
+co.ve
+com.ve
+e12.ve
+edu.ve
+gov.ve
+info.ve
+mil.ve
+net.ve
+org.ve
+web.ve
+#vg
+#vi
+co.vi
+com.vi
+k12.vi
+net.vi
+org.vi
+#vn
+com.vn
+net.vn
+org.vn
+edu.vn
+gov.vn
+int.vn
+ac.vn
+biz.vn
+info.vn
+name.vn
+pro.vn
+health.vn
+#vu
+#wf
+#ws
+com.ws
+net.ws
+org.ws
+gov.ws
+edu.ws
+#yt
+#xxx
+#ye
+com.ye
+co.ye
+ltd.ye
+me.ye
+net.ye
+org.ye
+plc.ye
+gov.ye
+#za
+ac.za
+co.za
+edu.za
+gov.za
+law.za
+mil.za
+nom.za
+org.za
+school.za
+ecape.school.za
+fs.school.za
+gp.school.za
+kzn.school.za
+mpm.za
+ncape.school.za
+lp.school.za
+nw.school.za
+wcape.school.za
+alt.za
+net.work.za
+ngo.za
+tm.za
+web.za
+agric.za
+grondar.za
+inca.za
+nis.za
+#zm
+ac.zm
+co.zm
+com.zm
+edu.zm
+gov.zm
+net.zm
+org.zm
+sch.zm
+#zw
+co.zw
+org.zw
+gov.zw
+ac.zw
+cloudfront.net
+compute.amazonaws.com
+us-east-1.amazonaws.com
+compute-1.amazonaws.com
+z-1.compute-1.amazonaws.com
+z-2.compute-1.amazonaws.com
+ap-northeast-1.compute.amazonaws.com
+ap-southeast-1.compute.amazonaws.com
+ap-southeast-2.compute.amazonaws.com
+eu-west-1.compute.amazonaws.com
+sa-east-1.compute.amazonaws.com
+us-gov-west-1.compute.amazonaws.com
+us-west-1.compute.amazonaws.com
+us-west-2.compute.amazonaws.com
+elasticbeanstalk.com
+elb.amazonaws.com
+s3.amazonaws.com
+s3-us-west-2.amazonaws.com
+s3-us-west-1.amazonaws.com
+s3-eu-west-1.amazonaws.com
+s3-ap-southeast-1.amazonaws.com
+s3-ap-southeast-2.amazonaws.com
+s3-ap-northeast-1.amazonaws.com
+s3-sa-east-1.amazonaws.com
+s3-us-gov-west-1.amazonaws.com
+s3-fips-us-gov-west-1.amazonaws.com
+s3-website-us-east-1.amazonaws.com
+s3-website-us-west-2.amazonaws.com
+s3-website-us-west-1.amazonaws.com
+s3-website-eu-west-1.amazonaws.com
+s3-website-ap-southeast-1.amazonaws.com
+s3-website-ap-southeast-2.amazonaws.com
+s3-website-ap-northeast-1.amazonaws.com
+s3-website-sa-east-1.amazonaws.com
+s3-website-us-gov-west-1.amazonaws.com
+betainabox.com
+ae.org
+ar.com
+br.com
+cn.com
+com.de
+de.com
+eu.com
+gb.com
+gb.net
+gr.com
+hu.com
+hu.net
+jp.net
+jpn.com
+kr.com
+no.com
+qc.com
+ru.com
+sa.com
+se.com
+se.net
+uk.com
+uk.net
+us.com
+us.org
+uy.com
+za.com
+c.la
+cloudcontrolled.com
+cloudcontrolapp.com
+co.ca
+co.nl
+co.no
+dreamhosters.com
+dyndns-at-home.com
+dyndns-at-work.com
+dyndns-blog.com
+dyndns-free.com
+dyndns-home.com
+dyndns-ip.com
+dyndns-mail.com
+dyndns-office.com
+dyndns-pics.com
+dyndns-remote.com
+dyndns-server.com
+dyndns-web.com
+dyndns-wiki.com
+dyndns-work.com
+dyndns.biz
+dyndns.info
+dyndns.org
+dyndns.tv
+at-band-camp.net
+ath.cx
+barrel-of-knowledge.info
+barrell-of-knowledge.info
+better-than.tv
+blogdns.com
+blogdns.net
+blogdns.org
+blogsite.org
+boldlygoingnowhere.org
+broke-it.net
+buyshouses.net
+cechire.com
+dnsalias.com
+dnsalias.net
+dnsalias.org
+dnsdojo.com
+dnsdojo.net
+dnsdojo.org
+does-it.net
+doesntexist.com
+doesntexist.org
+dontexist.com
+dontexist.net
+dontexist.org
+doomdns.com
+doomdns.org
+dvrdns.org
+dyn-o-saur.com
+dynalias.com
+dynalias.net
+dynalias.org
+dynathome.net
+dyndns.ws
+endofinternet.net
+endofinternet.org
+endoftheinternet.org
+est-a-la-maison.com
+est-a-la-masion.com
+est-le-patron.com
+est-mon-blogueur.com
+for-better.biz
+for-more.biz
+for-our.info
+for-some.biz
+for-the.biz
+forgot.her.name
+forgot.his.name
+from-ak.com
+from-al.com
+from-ar.com
+from-az.net
+from-ca.com
+from-co.net
+from-ct.com
+from-dc.com
+from-de.com
+from-fl.com
+from-ga.com
+from-hi.com
+from-ia.com
+from-id.com
+from-il.com
+from-in.com
+from-ks.com
+from-ky.com
+from-la.net
+from-ma.com
+from-md.com
+from-me.org
+from-mi.com
+from-mn.com
+from-mo.com
+from-ms.com
+from-mt.com
+from-nc.com
+from-nd.com
+from-ne.com
+from-nh.com
+from-nj.com
+from-nm.com
+from-nv.com
+from-ny.net
+from-oh.com
+from-ok.com
+from-or.com
+from-pa.com
+from-pr.com
+from-ri.com
+from-sc.com
+from-sd.com
+from-tn.com
+from-tx.com
+from-ut.com
+from-va.com
+from-vt.com
+from-wa.com
+from-wi.com
+from-wv.com
+from-wy.com
+ftpaccess.cc
+fuettertdasnetz.de
+game-host.org
+game-server.cc
+getmyip.com
+gets-it.net
+go.dyndns.org
+gotdns.com
+gotdns.org
+groks-the.info
+groks-this.info
+ham-radio-op.net
+here-for-more.info
+hobby-site.com
+hobby-site.org
+home.dyndns.org
+homedns.org
+homeftp.net
+homeftp.org
+homeip.net
+homelinux.com
+homelinux.net
+homelinux.org
+homeunix.com
+homeunix.net
+homeunix.org
+iamallama.com
+in-the-band.net
+is-a-anarchist.com
+is-a-blogger.com
+is-a-bookkeeper.com
+is-a-bruinsfan.org
+is-a-bulls-fan.com
+is-a-candidate.org
+is-a-caterer.com
+is-a-celticsfan.org
+is-a-chef.com
+is-a-chef.net
+is-a-chef.org
+is-a-conservative.com
+is-a-cpa.com
+is-a-cubicle-slave.com
+is-a-democrat.com
+is-a-designer.com
+is-a-doctor.com
+is-a-financialadvisor.com
+is-a-geek.com
+is-a-geek.net
+is-a-geek.org
+is-a-green.com
+is-a-guru.com
+is-a-hard-worker.com
+is-a-hunter.com
+is-a-knight.org
+is-a-landscaper.com
+is-a-lawyer.com
+is-a-liberal.com
+is-a-libertarian.com
+is-a-linux-user.org
+is-a-llama.com
+is-a-musician.com
+is-a-nascarfan.com
+is-a-nurse.com
+is-a-painter.com
+is-a-patsfan.org
+is-a-personaltrainer.com
+is-a-photographer.com
+is-a-player.com
+is-a-republican.com
+is-a-rockstar.com
+is-a-socialist.com
+is-a-soxfan.org
+is-a-student.com
+is-a-teacher.com
+is-a-techie.com
+is-a-therapist.com
+is-an-accountant.com
+is-an-actor.com
+is-an-actress.com
+is-an-anarchist.com
+is-an-artist.com
+is-an-engineer.com
+is-an-entertainer.com
+is-by.us
+is-certified.com
+is-found.org
+is-gone.com
+is-into-anime.com
+is-into-cars.com
+is-into-cartoons.com
+is-into-games.com
+is-leet.com
+is-lost.org
+is-not-certified.com
+is-saved.org
+is-slick.com
+is-uberleet.com
+is-very-bad.org
+is-very-evil.org
+is-very-good.org
+is-very-nice.org
+is-very-sweet.org
+is-with-theband.com
+isa-geek.com
+isa-geek.net
+isa-geek.org
+isa-hockeynut.com
+issmarterthanyou.com
+isteingeek.de
+istmein.de
+kicks-ass.net
+kicks-ass.org
+knowsitall.info
+land-4-sale.us
+lebtimnetz.de
+leitungsen.de
+likes-pie.com
+likescandy.com
+merseine.nu
+mine.nu
+misconfused.org
+mypets.ws
+myphotos.cc
+neat-url.com
+office-on-the.net
+on-the-web.tv
+podzone.net
+podzone.org
+readmyblog.org
+saves-the-whales.com
+scrapper-site.net
+scrapping.cc
+selfip.biz
+selfip.com
+selfip.info
+selfip.net
+selfip.org
+sells-for-less.com
+sells-for-u.com
+sells-it.net
+sellsyourhome.org
+servebbs.com
+servebbs.net
+servebbs.org
+serveftp.net
+serveftp.org
+servegame.org
+shacknet.nu
+simple-url.com
+space-to-rent.com
+stuff-4-sale.org
+stuff-4-sale.us
+teaches-yoga.com
+thruhere.net
+traeumtgerade.de
+webhop.biz
+webhop.info
+webhop.net
+webhop.org
+worse-than.tv
+writesthisblog.com
+a.ssl.fastly.net
+b.ssl.fastly.net
+global.ssl.fastly.net
+a.prod.fastly.net
+global.prod.fastly.net
+github.io
+ro.com
+appspot.com
+blogspot.be
+blogspot.bj
+blogspot.ca
+blogspot.cf
+blogspot.ch
+blogspot.co.at
+blogspot.co.il
+blogspot.co.nz
+blogspot.co.uk
+blogspot.com
+blogspot.com.ar
+blogspot.com.au
+blogspot.com.br
+blogspot.com.es
+blogspot.cv
+blogspot.cz
+blogspot.de
+blogspot.dk
+blogspot.fi
+blogspot.fr
+blogspot.gr
+blogspot.hk
+blogspot.hu
+blogspot.ie
+blogspot.in
+blogspot.it
+blogspot.jp
+blogspot.kr
+blogspot.mr
+blogspot.mx
+blogspot.nl
+blogspot.no
+blogspot.pt
+blogspot.re
+blogspot.ro
+blogspot.se
+blogspot.sg
+blogspot.sk
+blogspot.td
+blogspot.tw
+codespot.com
+googleapis.com
+googlecode.com
+herokuapp.com
+herokussl.com
+iki.fi
+biz.at
+info.at
+co.pl
+nyc.mn
+operaunite.com
+rhcloud.com
+priv.at
+za.net
+za.org
+
diff --git a/conf/lua/hfilter.lua b/conf/lua/hfilter.lua
new file mode 100644
index 000000000..b68061143
--- /dev/null
+++ b/conf/lua/hfilter.lua
@@ -0,0 +1,310 @@
+--
+-- Copyright (c) 2013, Alexey Savelyev
+-- E-mail: info@homeweb.ru
+-- WWW: http://homeweb.ru
+--
+
+--Rating for checks_hellohost and checks_hello: 5 - very hard, 4 - hard, 3 - meduim, 2 - low, 1 - very low
+local checks_hellohost = {
+['[.-]dynamic[.-]'] = 5, ['dynamic[.-][0-9]'] = 5, ['[0-9][.-]?dynamic'] = 5,
+['[.-]dyn[.-]'] = 5, ['dyn[.-][0-9]'] = 5, ['[0-9][.-]?dyn'] = 5,
+['[.-]clients?[.-]'] = 5, ['clients?[.-][0-9]'] = 5, ['[0-9][.-]?clients?'] = 5,
+['[.-]dynip[.-]'] = 5, ['dynip[.-][0-9]'] = 5, ['[0-9][.-]?dynip'] = 5,
+['[.-]broadband[.-]'] = 5, ['broadband[.-][0-9]'] = 5, ['[0-9][.-]?broadband'] = 5,
+['[.-]broad[.-]'] = 5, ['broad[.-][0-9]'] = 5, ['[0-9][.-]?broad'] = 5,
+['[.-]bredband[.-]'] = 5, ['bredband[.-][0-9]'] = 5, ['[0-9][.-]?bredband'] = 5,
+['[.-]nat[.-]'] = 5, ['nat[.-][0-9]'] = 5, ['[0-9][.-]?nat'] = 5,
+['[.-]pptp[.-]'] = 5, ['pptp[.-][0-9]'] = 5, ['[0-9][.-]?pptp'] = 5,
+['[.-]pppoe[.-]'] = 5, ['pppoe[.-][0-9]'] = 5, ['[0-9][.-]?pppoe'] = 5,
+['[.-]ppp[.-]'] = 5, ['ppp[.-][0-9]'] = 5, ['[0-9][.-]?ppp'] = 5,
+['[.-][a|x]?dsl[.-]'] = 4, ['[a|x]?dsl[.-]?[0-9]'] = 4, ['[0-9][.-]?[a|x]?dsl'] = 4,
+['[.-][a|x]?dsl-dynamic[.-]'] = 5, ['[a|x]?dsl-dynamic[.-]?[0-9]'] = 5, ['[0-9][.-]?[a|x]?dsl-dynamic'] = 5,
+['[.-][a|x]?dsl-line[.-]'] = 4, ['[a|x]?dsl-line[.-]?[0-9]'] = 4, ['[0-9][.-]?[a|x]?dsl-line'] = 4,
+['[.-]dhcp[.-]'] = 5, ['dhcp[.-][0-9]'] = 5, ['[0-9][.-]?dhcp'] = 5,
+['[.-]catv[.-]'] = 5, ['catv[.-][0-9]'] = 5, ['[0-9][.-]?catv'] = 5,
+['[.-]wifi[.-]'] = 5, ['wifi[.-][0-9]'] = 5, ['[0-9][.-]?wifi'] = 5,
+['[.-]unused-addr[.-]'] = 3, ['unused-addr[.-][0-9]'] = 3, ['[0-9][.-]?unused-addr'] = 3,
+['[.-]dial-?up[.-]'] = 5, ['dial-?up[.-][0-9]'] = 5, ['[0-9][.-]?dial-?up'] = 5,
+['[.-]gprs[.-]'] = 5, ['gprs[.-][0-9]'] = 5, ['[0-9][.-]?gprs'] = 5,
+['[.-]cdma[.-]'] = 5, ['cdma[.-][0-9]'] = 5, ['[0-9][.-]?cdma'] = 5,
+['[.-]homeuser[.-]'] = 5, ['homeuser[.-][0-9]'] = 5, ['[0-9][.-]?homeuser'] = 5,
+['[.-]in-?addr[.-]'] = 4, ['in-?addr[.-][0-9]'] = 4, ['[0-9][.-]?in-?addr'] = 4,
+['[.-]pool[.-]'] = 4, ['pool[.-][0-9]'] = 4, ['[0-9][.-]?pool'] = 4,
+['[.-]cable[.-]'] = 3, ['cable[.-][0-9]'] = 3, ['[0-9][.-]?cable'] = 3,
+['[.-]host[.-]'] = 2, ['host[.-][0-9]'] = 2, ['[0-9][.-]?host'] = 2,
+['[.-]customers[.-]'] = 1, ['customers[.-][0-9]'] = 1, ['[0-9][.-]?customers'] = 1
+}
+
+local checks_hello = {
+['localhost$'] = 5, ['\\.hfilter\\.ru'] = 5, ['^\\[*84\\.47\\.176\\.(70|71)'] = 5, ['^\\[*81\\.26\\.148\\.(66|67|68|69|70|71|72|73|74|75|76|77|79)'] = 5,
+['^(dsl)?(device|speedtouch)\\.lan$'] = 5,
+['\\.(lan|local|home|localdomain|intra|in-addr.arpa|priv|online|user|veloxzon)$'] = 5,
+['^\\[*127\\.'] = 5, ['^\\[*10\\.'] = 5, ['^\\[*172\\.16\\.'] = 5, ['^\\[*192\\.168\\.'] = 5,
+--bareip
+['^\\[*\\d+[x.-]\\d+[x.-]\\d+[x.-]\\d+\\]*$'] = 4
+}
+
+local function trim1(s)
+ return (s:gsub("^%s*(.-)%s*$", "%1"))
+end
+
+local function check_regexp(str, regexp_text)
+ local re = regexp.get_cached(regexp_text)
+ if not re then re = regexp.create(regexp_text, 'i') end
+ if re:match(str) then return true end
+return false
+end
+
+local function split(str, delim, maxNb)
+ -- Eliminate bad cases...
+ if string.find(str, delim) == nil then
+ return { str }
+ end
+ if maxNb == nil or maxNb < 1 then
+ maxNb = 0 -- No limit
+ end
+ local result = {}
+ local pat = "(.-)" .. delim .. "()"
+ local nb = 0
+ local lastPos
+ for part, pos in string.gmatch(str, pat) do
+ nb = nb + 1
+ result[nb] = part
+ lastPos = pos
+ if nb == maxNb then break end
+ end
+ -- Handle the last field
+ if nb ~= maxNb then
+ result[nb + 1] = string.sub(str, lastPos)
+ end
+ return result
+end
+
+local function check_fqdn(domain)
+ if check_regexp(domain, '(?=^.{4,255}$)(^((?!-)[a-zA-Z0-9-]{1,63}(?<!-)\\.)+[a-zA-Z]{2,63}$)') then
+ return true
+ end
+return false
+end
+
+-- host: host for check
+-- symbol_suffix: suffix for symbol
+-- eq_ip: ip for comparing or empty string
+-- eq_host: host for comparing or empty string
+local function check_host(task, host, symbol_suffix, eq_ip, eq_host)
+
+ local function check_host_cb_mx_a(resolver, to_resolve, results, err)
+ task:inc_dns_req()
+ if not results then
+ task:insert_result('HFILTER_' .. symbol_suffix .. '_NORESOLVE_MX', 1.0)
+ end
+ end
+ local function check_host_cb_mx(resolver, to_resolve, results, err)
+ task:inc_dns_req()
+ if not results then
+ task:insert_result('HFILTER_' .. symbol_suffix .. '_NORES_A_OR_MX', 1.0)
+ else
+ for _,mx in pairs(results) do
+ if mx['name'] then
+ task:get_resolver():resolve_a(task:get_session(), task:get_mempool(), mx['name'], check_host_cb_mx_a)
+ end
+ end
+ end
+ end
+ local function check_host_cb_a(resolver, to_resolve, results, err)
+ task:inc_dns_req()
+
+ if not results then
+ task:get_resolver():resolve_mx(task:get_session(), task:get_mempool(), host, check_host_cb_mx)
+ elseif eq_ip ~= '' then
+ for _,result in pairs(results) do
+ if result:to_string() == eq_ip then
+ return true
+ end
+ end
+ task:insert_result('HFILTER_' .. symbol_suffix .. '_IP_A', 1.0)
+ end
+ end
+
+ if host then
+ host = string.lower(host)
+ else
+ return false
+ end
+ if eq_host then
+ eq_host = string.lower(eq_host)
+ else
+ eq_host = ''
+ end
+
+ if check_fqdn(host) then
+ if eq_host == '' or eq_host ~= host then
+ task:get_resolver():resolve_a(task:get_session(), task:get_mempool(), host, check_host_cb_a)
+ end
+ else
+ task:insert_result('HFILTER_' .. symbol_suffix .. '_NOT_FQDN', 1.0)
+ end
+
+return true
+end
+
+--
+local function hfilter(task)
+ local recvh = task:get_received_headers()
+
+ if table.maxn(recvh) == 0 then
+ return false
+ end
+
+ --IP--
+ local ip = false
+ local rip = task:get_from_ip()
+ if rip then
+ ip = rip:to_string()
+ if ip and ip == '0.0.0.0' then
+ ip = false
+ end
+ end
+
+ --HOSTNAME--
+ local r = recvh[1]
+ local hostname = false
+ local hostname_lower = false
+ if r['real_hostname'] and ( r['real_hostname'] ~= 'unknown' or not check_regexp(r['real_hostname'], '^\\d+\\.\\d+\\.\\d+\\.\\d+$') ) then
+ hostname = r['real_hostname']
+ hostname_lower = string.lower(hostname)
+ end
+
+ --HELO--
+ local helo = task:get_helo()
+ local helo_lower = false
+ if helo then
+ helo_lower = string.lower(helo)
+ else
+ helo = false
+ helo_lower = false
+ end
+
+ --MESSAGE--
+ local message = task:get_message()
+
+ --RULES--RULES--RULES--
+
+ -- Check's HELO
+ local checks_hello_found = false
+ if helo then
+ -- Regexp check HELO
+ for regexp,weight in pairs(checks_hello) do
+ if check_regexp(helo_lower, regexp) then
+ task:insert_result('HFILTER_HELO_' .. weight, 1.0)
+ checks_hello_found = true
+ break
+ end
+ end
+ if not checks_hello_found then
+ for regexp,weight in pairs(checks_hellohost) do
+ if check_regexp(helo_lower, regexp) then
+ task:insert_result('HFILTER_HELO_' .. weight, 1.0)
+ checks_hello_found = true
+ break
+ end
+ end
+ end
+
+ --FQDN check HELO
+ if ip then
+ check_host(task, helo, 'HELO', ip, hostname)
+ end
+ end
+
+ --
+ local function check_hostname(hostname_res)
+ -- Check regexp HOSTNAME
+ for regexp,weight in pairs(checks_hellohost) do
+ if check_regexp(hostname_res, regexp) then
+ task:insert_result('HFILTER_HOSTNAME_' .. weight, 1.0)
+ break
+ end
+ end
+ end
+ local function hfilter_hostname_ptr(resolver, to_resolve, results, err)
+ task:inc_dns_req()
+ if results then
+ check_hostname(results[1])
+ end
+ end
+
+ -- Check's HOSTNAME
+ if hostname then
+ if not checks_hello_found then
+ check_hostname(hostname)
+ end
+ else
+ task:insert_result('HFILTER_HOSTNAME_NOPTR', 1.00)
+ if ip and not checks_hello_found then
+ task:get_resolver():resolve_ptr(task:get_session(), task:get_mempool(), ip, hfilter_hostname_ptr)
+ end
+ end
+
+ -- MAILFROM checks --
+ local from = task:get_from()
+ if from then
+ --FROM host check
+ for _,fr in ipairs(from) do
+ local fr_split = split(fr['addr'], '@', 0)
+ if table.maxn(fr_split) == 2 then
+ check_host(task, fr_split[2], 'FROMHOST', '', '')
+ end
+ end
+ end
+
+ --Message ID host check
+ local message_id = task:get_message_id()
+ if message_id then
+ local mid_split = split(message_id, '@', 0)
+ if table.maxn(mid_split) == 2 and not string.find(mid_split[2], "local") then
+ if not check_fqdn(mid_split[2]) then
+ task:insert_result('HFILTER_MID_NOT_FQDN', 1.00)
+ end
+ end
+ end
+
+ -- Links checks
+ local parts = task:get_text_parts()
+ if parts then
+ --One text part--
+ if table.maxn(parts) > 0 and parts[1]:get_content() then
+ local part_text = trim1(parts[1]:get_content())
+ local total_part_len = string.len(part_text)
+ if total_part_len > 0 then
+ local urls = task:get_urls()
+ if urls then
+ local total_url_len = 0
+ for _,url in ipairs(urls) do
+ total_url_len = total_url_len + string.len(url:get_text())
+ end
+ if total_url_len > 0 then
+ if total_url_len + 7 > total_part_len then
+ task:insert_result('HFILTER_URL_ONLY', 1.00)
+ else
+ if not string.find(part_text, "\n") then
+ task:insert_result('HFILTER_URL_ONELINE', 1.00)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+
+ return false
+end
+
+rspamd_config:register_symbols(hfilter, 1.0,
+"HFILTER_HELO_1", "HFILTER_HELO_2", "HFILTER_HELO_3", "HFILTER_HELO_4", "HFILTER_HELO_5",
+"HFILTER_HOSTNAME_1", "HFILTER_HOSTNAME_2", "HFILTER_HOSTNAME_3", "HFILTER_HOSTNAME_4", "HFILTER_HOSTNAME_5",
+"HFILTER_HELO_NORESOLVE_MX", "HFILTER_HELO_NORES_A_OR_MX", "HFILTER_HELO_IP_A", "HFILTER_HELO_NOT_FQDN",
+"HFILTER_FROMHOST_NORESOLVE_MX", "HFILTER_FROMHOST_NORES_A_OR_MX", "HFILTER_FROMHOST_NOT_FQDN",
+"HFILTER_MID_NOT_FQDN",
+"HFILTER_HOSTNAME_NOPTR",
+"HFILTER_URL_ONLY", "HFILTER_URL_ONELINE");
diff --git a/conf/lua/rspamd.lua b/conf/lua/rspamd.lua
index 822f6447f..e8b585837 100644
--- a/conf/lua/rspamd.lua
+++ b/conf/lua/rspamd.lua
@@ -68,7 +68,6 @@ reconf['DATE_IN_PAST'] = function(task)
return false
end
-
local function file_exists(filename)
local file = io.open(filename)
if file then
@@ -79,6 +78,10 @@ local function file_exists(filename)
end
end
+if file_exists('hfilter.lua') then
+ dofile('hfilter.lua')
+end
+
if file_exists('rspamd.local.lua') then
dofile('rspamd.local.lua')
end
diff --git a/conf/metrics.conf b/conf/metrics.conf
index 727bea6da..13b9796af 100644
--- a/conf/metrics.conf
+++ b/conf/metrics.conf
@@ -18,7 +18,7 @@ metric {
name = "FORGED_OUTLOOK_TAGS";
}
symbol {
- weight = 5.0;
+ weight = 0.30;
description = "Sender is forged (different From: header and smtp MAIL FROM: addresses)";
name = "FORGED_SENDER";
}
@@ -288,7 +288,7 @@ metric {
name = "MISSING_MID";
}
symbol {
- weight = 3.0;
+ weight = 1.0;
description = "Recipients are not the same as RCPT TO: mail command";
name = "FORGED_RECIPIENTS";
}
@@ -322,20 +322,71 @@ metric {
description = "One received header with 'bad' patterns inside";
name = "ONCE_RECEIVED_STRICT";
}
+ symbol { name = "RBL_SPAMHAUS"; weight = 0.0; description = "From address is listed in zen"; }
+ symbol { name = "RBL_SPAMHAUS_SBL"; weight = 2.0; description = "From address is listed in zen sbl"; }
+ symbol { name = "RBL_SPAMHAUS_CSS"; weight = 2.0; description = "From address is listed in zen css"; }
+ symbol { name = "RBL_SPAMHAUS_XBL"; weight = 4.0; description = "From address is listed in zen xbl"; }
+ symbol { name = "RBL_SPAMHAUS_PBL"; weight = 2.0; description = "From address is listed in zen pbl"; }
+ symbol { name = "RECEIVED_SPAMHAUS_XBL"; weight = 3.0; description = "Received address is listed in zen pbl"; }
+ symbol {
+ weight = 2.0;
+ description = "From address is listed in senderscore.com BL";
+ name = "RBL_SENDERSCORE";
+ }
+ symbol {
+ weight = 2.0;
+ description = "From address is listed in mailspike.com BL";
+ name = "RBL_MAILSPIKE";
+ }
symbol {
weight = 1.0;
- description = "Received headers contains addresses from zen spamhaus RBL";
- name = "RBL_ZEN";
+ name = "RBL_SORBS";
+ description = "From address is listed in SORBS RBL";
+ }
+ symbol {
+ weight = 2.5;
+ name = "RBL_SORBS_HTTP";
+ description = "List of Open HTTP Proxy Servers.";
+ }
+ symbol {
+ weight = 2.5;
+ name = "RBL_SORBS_SOCKS";
+ description = "List of Open SOCKS Proxy Servers.";
}
symbol {
weight = 1.0;
- description = "Received headers contains addresses from senderscore.com RBL";
- name = "RBL_SENDERSCORE";
+ name = "RBL_SORBS_MISC";
+ description = "List of open Proxy Servers not listed in the SOCKS or HTTP lists.";
+ }
+ symbol {
+ weight = 3.0;
+ name = "RBL_SORBS_SMTP";
+ description = "List of Open SMTP relay servers.";
+ }
+ symbol {
+ weight = 1.5;
+ name = "RBL_SORBS_RECENT";
+ description = "List of hosts that have been noted as sending spam/UCE/UBE to the admins of SORBS within the last 28 days (includes new.spam.dnsbl.sorbs.net).";
+ }
+ symbol {
+ weight = 0.4;
+ name = "RBL_SORBS_WEB";
+ description = "List of web (WWW) servers which have spammer abusable vulnerabilities (e.g. FormMail scripts)";
+ }
+ symbol {
+ weight = 2.0;
+ name = "RBL_SORBS_DUL";
+ description = "Dynamic IP Address ranges (NOT a Dial Up list!)";
}
symbol {
weight = 1.0;
- description = "Received headers contains addresses from mailspike.com RBL";
- name = "RBL_MAILSPIKE";
+ name = "RBL_SORBS_BLOCK";
+ description = "List of hosts demanding that they never be tested by SORBS.";
+ }
+ symbol {
+ weight = 1.0;
+ name = "RBL_SORBS_ZOMBIE";
+ description = "List of networks hijacked from their original owners, some of which have already used for spamming.";
}
symbol {
weight = 3.0;
@@ -408,22 +459,22 @@ metric {
name = "BAYES_HAM";
}
symbol {
- weight = 1.0;
+ weight = 5.0;
description = "Generic fuzzy hash match";
- name = "R_FUZZY";
+ name = "FUZZY_UNKNOWN";
}
symbol {
- weight = 1.0;
+ weight = 10.0;
description = "Denied fuzzy hash";
name = "FUZZY_DENIED";
}
symbol {
- weight = 1.0;
+ weight = 5.0;
description = "Probable fuzzy hash";
name = "FUZZY_PROB";
}
symbol {
- weight = 1.0;
+ weight = -2.1;
description = "Whitelisted fuzzy hash";
name = "FUZZY_WHITE";
}
@@ -493,11 +544,16 @@ metric {
name = "WS_SURBL_MULTI";
}
symbol {
- weight = 9.500000;
+ weight = 4.500000;
description = "rambler.ru uribl";
name = "RAMBLER_URIBL";
}
symbol {
+ weight = 5.500000;
+ description = "DBL uribl";
+ name = "DBL";
+ }
+ symbol {
weight = 7.5;
description = "uribl.com black url";
name = "URIBL_BLACK";
@@ -637,4 +693,26 @@ metric {
description = "Message date is in past";
name = "DATE_IN_PAST";
}
+# hfilter symbols
+ symbol { weight = 1.00; name = "HFILTER_HELO_1"; description = "Helo host checks (very low)"; }
+ symbol { weight = 2.00; name = "HFILTER_HELO_2"; description = "Helo host checks (low)"; }
+ symbol { weight = 3.00; name = "HFILTER_HELO_3"; description = "Helo host checks (medium)"; }
+ symbol { weight = 3.50; name = "HFILTER_HELO_4"; description = "Helo host checks (hard)"; }
+ symbol { weight = 4.00; name = "HFILTER_HELO_5"; description = "Helo host checks (very hard)"; }
+ symbol { weight = 1.00; name = "HFILTER_HOSTNAME_1"; description = "Hostname checks (very low)"; }
+ symbol { weight = 2.00; name = "HFILTER_HOSTNAME_2"; description = "Hostname checks (low)"; }
+ symbol { weight = 3.00; name = "HFILTER_HOSTNAME_3"; description = "Hostname checks (medium)"; }
+ symbol { weight = 3.50; name = "HFILTER_HOSTNAME_4"; description = "Hostname checks (hard)"; }
+ symbol { weight = 4.00; name = "HFILTER_HOSTNAME_5"; description = "Hostname checks (very hard)"; }
+ symbol { weight = 1.50; name = "HFILTER_HELO_NORESOLVE_MX"; description = "MX found in Helo and no resolve"; }
+ symbol { weight = 2.00; name = "HFILTER_HELO_NORES_A_OR_MX"; description = "Helo no resolve to A or MX"; }
+ symbol { weight = 2.50; name = "HFILTER_HELO_IP_A"; description = "Helo A IP != hostname IP"; }
+ symbol { weight = 3.00; name = "HFILTER_HELO_NOT_FQDN"; description = "Helo not FQDN"; }
+ symbol { weight = 1.50; name = "HFILTER_FROMHOST_NORESOLVE_MX"; description = "MX found in FROM host and no resolve"; }
+ symbol { weight = 3.00; name = "HFILTER_FROMHOST_NORES_A_OR_MX"; description = "FROM host no resolve to A or MX"; }
+ symbol { weight = 4.00; name = "HFILTER_FROMHOST_NOT_FQDN"; description = "FROM host not FQDN"; }
+ symbol { weight = 0.50; name = "HFILTER_MID_NOT_FQDN"; description = "Message-id host not FQDN"; }
+ symbol { weight = 4.00; name = "HFILTER_HOSTNAME_NOPTR"; description = "No PTR for IP"; }
+ symbol { weight = 3.50; name = "HFILTER_URL_ONLY"; description = "URL only in body"; }
+ symbol { weight = 2.00; name = "HFILTER_URL_ONELINE"; description = "One line URL and text in body"; }
}
diff --git a/conf/modules.conf b/conf/modules.conf
index 2b7d5b8b8..41296b7bf 100644
--- a/conf/modules.conf
+++ b/conf/modules.conf
@@ -1,24 +1,28 @@
# Rspamd modules configuration
fuzzy_check {
- servers = "highsecure.ru:11335";
- symbol = "R_FUZZY";
min_bytes = 300;
- max_score = 10;
- mime_types = "application/pdf";
- fuzzy_map = {
- FUZZY_DENIED {
- weight = 10.0;
- flag = 1
- }
- FUZZY_PROB {
- weight = 5.0;
- flag = 2
- }
- FUZZY_WHITE {
- weight = -2.1;
- flag = 3
- }
- }
+ rule {
+ servers = "highsecure.ru:11335";
+ symbol = "FUZZY_UNKNOWN";
+ mime_types = "application/pdf";
+ max_score = 20.0;
+ read_only = yes;
+ skip_unknown = yes;
+ fuzzy_map = {
+ FUZZY_DENIED {
+ max_score = 20.0;
+ flag = 1
+ }
+ FUZZY_PROB {
+ max_score = 10.0;
+ flag = 2
+ }
+ FUZZY_WHITE {
+ max_score = 2.0;
+ flag = 3
+ }
+ }
+ }
}
forged_recipients {
symbol_sender = "FORGED_SENDER";
@@ -67,28 +71,34 @@ rbl {
default_from = true;
rbls {
- spamhaus_zen {
- symbol = "RBL_ZEN";
- rbl = "zen.spamhaus.org";
- ipv4 = true;
- ipv6 = true;
- }
- spamhaus_pbl {
- symbol = "RECEIVED_PBL";
- rbl = "pbl.spamhaus.org";
- ipv4 = true;
- ipv6 = true;
- received = true;
- from = false;
- }
- spamhaus_pbl {
- symbol = "RECEIVED_XBL";
+
+ spamhaus {
+ symbol = "RBL_SPAMHAUS";
+ rbl = "zen.spamhaus.org";
+ ipv4 = true;
+ ipv6 = true;
+ unknown = false;
+ returncodes {
+ RBL_SPAMHAUS_SBL = "127.0.0.2";
+ RBL_SPAMHAUS_CSS = "127.0.0.3";
+ RBL_SPAMHAUS_XBL = "127.0.0.4";
+ RBL_SPAMHAUS_XBL = "127.0.0.5";
+ RBL_SPAMHAUS_XBL = "127.0.0.6";
+ RBL_SPAMHAUS_XBL = "127.0.0.7";
+ RBL_SPAMHAUS_PBL = "127.0.0.10";
+ RBL_SPAMHAUS_PBL = "127.0.0.11";
+ }
+ }
+
+ spamhaus_xbl {
+ symbol = "RECEIVED_SPAMHAUS_XBL";
rbl = "xbl.spamhaus.org";
ipv4 = true;
ipv6 = true;
received = true;
from = false;
}
+
mailspike {
symbol = "RBL_MAILSPIKE";
rbl = "bl.mailspike.net";
@@ -97,6 +107,22 @@ rbl {
symbol = "RBL_SENDERSCORE";
rbl = "bl.score.senderscore.com";
}
+ sorbs {
+ symbol = "RBL_SORBS";
+ rbl = "dnsbl.sorbs.net";
+ returncodes {
+ #http://www.sorbs.net/general/using.shtml
+ RBL_SORBS_HTTP = "127.0.0.2"
+ RBL_SORBS_SOCKS = "127.0.0.3"
+ RBL_SORBS_MISC = "127.0.0.4"
+ RBL_SORBS_SMTP = "127.0.0.5"
+ RBL_SORBS_RECENT = "127.0.0.6"
+ RBL_SORBS_WEB = "127.0.0.7"
+ RBL_SORBS_DUL = "127.0.0.10"
+ RBL_SORBS_BLOCK = "127.0.0.8"
+ RBL_SORBS_ZOMBIE = "127.0.0.9"
+ }
+ }
}
}
diff --git a/conf/options.conf b/conf/options.conf
index 841372ec2..1dd1ebfb8 100644
--- a/conf/options.conf
+++ b/conf/options.conf
@@ -6,6 +6,7 @@ options {
raw_mode = false;
one_shot = false;
dns_timeout = 1s;
+ dns_sockets = 16;
dns_retransmits = 5;
cache_file = "$DBDIR/symbols.cache";
map_watch_interval = 1min;
diff --git a/config.h.in b/config.h.in
index c7e44d846..e387d5d1c 100644
--- a/config.h.in
+++ b/config.h.in
@@ -145,6 +145,7 @@
#cmakedefine HAVE_FLOCK 1
#cmakedefine HAVE_TANHL 1
+#cmakedefine HAVE_TANH 1
#cmakedefine HAVE_EXPL 1
#cmakedefine HAVE_EXP2L 1
diff --git a/contrib/lgpl/CMakeLists.txt b/contrib/lgpl/CMakeLists.txt
index 56cedff89..39191620a 100644
--- a/contrib/lgpl/CMakeLists.txt
+++ b/contrib/lgpl/CMakeLists.txt
@@ -12,9 +12,7 @@ IF(GLIB2_VERSION VERSION_LESS "2.16")
ADD_LIBRARY(glibadditions SHARED ${LIBGLIBSRC})
SET_TARGET_PROPERTIES(glibadditions PROPERTIES COMPILE_FLAGS "-I${CMAKE_SOURCE_DIR}/contrib/lgpl")
- TARGET_LINK_LIBRARIES(glibadditions ${CMAKE_REQUIRED_LIBRARIES})
- TARGET_LINK_LIBRARIES(glibadditions ${GLIB2_LIBRARIES})
- TARGET_LINK_LIBRARIES(glibadditions pcre)
+ TARGET_LINK_LIBRARIES(glibadditions ${RSPAMD_REQUIRED_LIBRARIES})
SET_TARGET_PROPERTIES(glibadditions PROPERTIES VERSION ${RSPAMD_VERSION})
diff --git a/debian/changelog b/debian/changelog
index 60f8eddfc..3d34bc28c 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,33 @@
+rspamd (0.6.6-1) unstable; urgency=low
+
+ * Upgrade to 0.6.6
+
+ -- Vsevolod Stakhov <vsevolod@highsecure.ru> Fri, 27 Dec 2013 14:55:00 +0000
+
+rspamd (0.6.5-1) unstable; urgency=low
+
+ * Upgrade to 0.6.5
+
+ -- Vsevolod Stakhov <vsevolod@highsecure.ru> Wed, 20 Dec 2013 15:50:00 +0000
+
+rspamd (0.6.4-1) unstable; urgency=low
+
+ * Upgrade to 0.6.4
+
+ -- Vsevolod Stakhov <vsevolod@highsecure.ru> Wed, 18 Dec 2013 15:24:00 +0000
+
+rspamd (0.6.3-1) unstable; urgency=low
+
+ * Upgrade to 0.6.3
+
+ -- Vsevolod Stakhov <vsevolod@highsecure.ru> Tue, 10 Dec 2013 19:17:00 +0000
+
+rspamd (0.6.2-1) unstable; urgency=low
+
+ * Upgrade to 0.6.2
+
+ -- Vsevolod Stakhov <vsevolod@highsecure.ru> Wed, 4 Dec 2013 16:43:00 +0000
+
rspamd (0.6.1-1) unstable; urgency=low
* Upgrade to 0.6.1
diff --git a/debian/control b/debian/control
index 9b67feece..0611624c4 100644
--- a/debian/control
+++ b/debian/control
@@ -1,9 +1,9 @@
Source: rspamd
Section: mail
-Priority: extra
+Priority: optional
Maintainer: Vsevolod Stakhov <vsevolod@highsecure.ru>
-Build-Depends: debhelper (>= 7.0.50~), dpkg-dev (>= 1.16.1~), cmake, libevent-dev (>= 1.3), libglib2.0-dev (>= 2.16.0), libgmime-2.6-dev, liblua5.2-dev, libpcre3-dev, cdbs, libssl-dev (>= 1.0), libjudy-dev, libcurl4-openssl-dev
-Standards-Version: 3.9.3
+Build-Depends: debhelper (>= 7.0.50~), dpkg-dev (>= 1.16.1~), cmake, libevent-dev (>= 1.3), libglib2.0-dev (>= 2.16.0), libgmime-2.6-dev, liblua5.2-dev | liblua5.1-dev | liblua5.1-0-dev, libpcre3-dev, cdbs, libssl-dev (>= 1.0), libcurl4-openssl-dev
+Standards-Version: 3.9.5
Homepage: https://bitbucket.org/vstakhov/rspamd/
Vcs-Hg: https://bitbucket.org/vstakhov/rspamd/
Vcs-Browser: https://bitbucket.org/vstakhov/rspamd/src
diff --git a/debian/postrm b/debian/postrm
index 4af96e131..14735e0a2 100644
--- a/debian/postrm
+++ b/debian/postrm
@@ -1,24 +1,9 @@
#!/bin/sh
# postrm script for rspamd
-#
-# see: dh_installdeb(1)
+#DEBHELPER#
set -e
-# summary of how this script can be called:
-# * <postrm> `remove'
-# * <postrm> `purge'
-# * <old-postrm> `upgrade' <new-version>
-# * <new-postrm> `failed-upgrade' <old-version>
-# * <new-postrm> `abort-install'
-# * <new-postrm> `abort-install' <old-version>
-# * <new-postrm> `abort-upgrade' <old-version>
-# * <disappearer's-postrm> `disappear' <overwriter>
-# <overwriter-version>
-# for details, see http://www.debian.org/doc/debian-policy/ or
-# the debian-policy package
-
-
case "$1" in
purge|remove|abort-install|disappear)
# find first and last SYSTEM_UID numbers
@@ -81,11 +66,6 @@ case "$1" in
;;
esac
-# dh_installdeb will replace this with shell code automatically
-# generated by other debhelper scripts.
-
-#DEBHELPER#
-
exit 0
diff --git a/debian/preinst b/debian/preinst
index 9127115e1..3588f2f5b 100644
--- a/debian/preinst
+++ b/debian/preinst
@@ -1,19 +1,9 @@
#!/bin/sh
-# preinst script for rmilter
-#
-# see: dh_installdeb(1)
+# preinst script for rspamd
+#DEBHELPER#
set -e
-# summary of how this script can be called:
-# * <new-preinst> `install'
-# * <new-preinst> `install' <old-version>
-# * <new-preinst> `upgrade' <old-version>
-# * <old-preinst> `abort-upgrade' <new-version>
-# for details, see http://www.debian.org/doc/debian-policy/ or
-# the debian-policy package
-
-
case "$1" in
install)
SERVER_HOME=/var/lib/rspamd
@@ -77,9 +67,4 @@ case "$1" in
;;
esac
-# dh_installdeb will replace this with shell code automatically
-# generated by other debhelper scripts.
-
-#DEBHELPER#
-
exit 0
diff --git a/debian/rules b/debian/rules
index 2a50cc64b..ce09d5128 100755
--- a/debian/rules
+++ b/debian/rules
@@ -2,20 +2,25 @@
#export DEB_BUILD_MAINT_OPTIONS=hardening=+all
+export DEB_LDFLAGS_MAINT_APPEND=-Wl,--as-needed
+
include /usr/share/dpkg/buildflags.mk
include /usr/share/cdbs/1/rules/debhelper.mk
include /usr/share/cdbs/1/class/cmake.mk
-DEB_CMAKE_NORMAL_ARGS+= -DCONFDIR=/etc/rspamd \
+DEB_CMAKE_NORMAL_ARGS+= -DCONFDIR=/etc/rspamd \
-DMANDIR=/usr/share/man \
-DRUNDIR=/var/lib/rspamd \
-DDBDIR=/var/lib/rspamd \
-DLOGDIR=/var/log/rspamd \
-DPLUGINSDIR=/usr/share/rspamd \
+ -DEXAMPLESDIR=/usr/share/examples/rspamd \
-DLIBDIR=/usr/lib \
-DINCLUDEDIR=/usr/include \
-DNO_SHARED=ON \
-DDEBIAN_BUILD=1 \
+ -DINSTALL_EXAMPLES=ON \
+ -DFORCE_GMIME24=ON \
-DRSPAMD_GROUP=rspamd \
-DRSPAMD_USER=rspamd
clean::
diff --git a/debian/source.lintian-overrides b/debian/source.lintian-overrides
deleted file mode 100644
index 56d0deeaa..000000000
--- a/debian/source.lintian-overrides
+++ /dev/null
@@ -1,3 +0,0 @@
-# Actually rspamd does not contain any gpl code except of these package,
-# so ignore unsafe link with openssl
-rspamd: possible-gpl-code-linked-with-openssl
diff --git a/doc/Makefile b/doc/Makefile
new file mode 100644
index 000000000..bcc5b3a9f
--- /dev/null
+++ b/doc/Makefile
@@ -0,0 +1,12 @@
+# A simple makefile to generate documentation from .md using pandoc
+
+PANDOC ?= pandoc
+
+all: man
+
+man: rspamd.8 rspamc.1
+
+rspamd.8: rspamd.8.md
+ $(PANDOC) -s -f markdown -t man -o rspamd.8 rspamd.8.md
+rspamc.1: rspamc.1.md
+ $(PANDOC) -s -f markdown -t man -o rspamc.1 rspamc.1.md
diff --git a/doc/makeman.sh b/doc/makeman.sh
deleted file mode 100755
index 176f37b25..000000000
--- a/doc/makeman.sh
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/sh
-# Makes manual pages from pods
-
-POD2MAN="pod2man"
-VERSION="unknown"
-CMAKEFILE="../CMakeLists.txt"
-
-# Extract release version
-if [ -f ${CMAKEFILE} ] ; then
- _mjver=`fgrep 'SET(RSPAMD_VERSION_MAJOR' ${CMAKEFILE} | sed -e 's/^.*RSPAMD_VERSION_MAJOR \([0-9]\).*/\1/'`
- _miver=`fgrep 'SET(RSPAMD_VERSION_MINOR' ${CMAKEFILE} | sed -e 's/^.*RSPAMD_VERSION_MINOR \([0-9]\).*/\1/'`
- _pver=`fgrep 'SET(RSPAMD_VERSION_PATCH' ${CMAKEFILE} | sed -e 's/^.*RSPAMD_VERSION_PATCH \([0-9]\).*/\1/'`
- VERSION="${_mjver}.${_miver}.${_pver}"
-fi
-
-pod2man -c "Rspamd documentation" -n rspamd -s 8 -r "rspamd-${VERSION}" < rspamd.pod > rspamd.8
-pod2man -c "Rspamd documentation" -n rspamc -s 1 -r "rspamd-${VERSION}" < rspamc.pod > rspamc.1
diff --git a/doc/markdown/architecture/index.md b/doc/markdown/architecture/index.md
new file mode 100644
index 000000000..33669f2d5
--- /dev/null
+++ b/doc/markdown/architecture/index.md
@@ -0,0 +1,117 @@
+# Rspamd architecture
+
+## Introduction
+
+Rspamd is a universal spam filtering system based on event-driven processing
+model. It means that rspamd is intended not to block anywhere in the code. To
+process messages rspamd uses a set of so called `rules`. Each `rule` is a symbolic
+name associated with some message property. For example, we can define the following
+rules:
+
+- SPF_ALLOW - means that a message is validated by SPF;
+- BAYES_SPAM - means that a message is statistically considered as spam;
+- FORGED_OUTLOOK_MID - message ID seems to be forged for Outlook MUA.
+
+Rules are defined by [modules](../modules/). So far, if there is a module that
+performs SPF checks it may define several rules according to SPF policy:
+
+- SPF_ALLOW - a sender is allowed to send messages for this domain;
+- SPF_DENY - a sender is denied by SPF policy;
+- SPF_SOFTFAIL - there is no affinity defined by SPF policy.
+
+Rspamd supports two main types of modules: internal written in C and external
+written in Lua. There is no real difference between these two types with the exception
+that C modules are embeded all the time and can be enabled in `filters` attribute
+in the `options` section of the config:
+
+~~~nginx
+options {
+ filters = "regexp,surbl,spf,dkim,fuzzy_check,chartable,email";
+ ...
+}
+~~~
+
+## Metrics
+
+Rules in rspamd, defines merely a logic of checks, however it is required to
+set up weights for each rule. Weight means `significance` in terms of rspamd. So
+far, rules with greater absolute value of weight are considered as more important
+than the recent rules. The weight of rules is defined in `metrics`. Each metric
+is a set of grouped rules with specific weights. For example, we may define the
+following weights for our SPF rules:
+
+- SPF_ALLOW: -1
+- SPF_DENY: 2
+- SPF_SOFTFAIL: 0.5
+
+Positive weights means that this rule turns message to more spammy, while negative
+means the opposite.
+
+### Rules scheduler
+
+To avoid unnecessary checks rspamd uses scheduler of rules for each message. So far,
+if a message is considered as `definite spam` then further checks are not performed.
+This scheduler is rather naive and it performs the following logic:
+
+- select negative rules *before* positive ones to prevent false positives;
+- prefer rules with the following characteristics:
+ - frequent rules;
+ - rules with more weight;
+ - faster rules
+
+These optimizations can filter definite spam more quickly than a generic queue.
+
+## Actions
+
+Another important property of metrics is their actions set. This set defines recommended
+actions for a message if it reach a certain score defined by all rules triggered.
+Rspamd defines the following actions:
+
+- **No action**: a message is likely ham;
+- **Greylist**: greylist message is it is not certainly ham;
+- **Add header**: a message is likely spam, so add a specific header;
+- **Rewrite subject**: a message is likely spam, so rewrite its subject;
+- **Reject**: a message is very likely spam, so reject it completely
+
+These actions are just recommendations for MTA and are not to be strictly followed.
+For all actions that are greater or equal than *greylist* it is recommended to
+perform explicit greylisting. *Add header* and *rewrite subject* actions are very
+close in semantics and are both considered as `probable spam`. `Reject` is a
+strong rule that usually means that a message should be really rejected by MTA.
+The triggering score for these actions should be specified according to their logic
+priorities. If two actions have the same weight, the result is unspecified.
+
+## Rules weight
+
+The weights of rules is not necessarily constant. For example, for statistics rules
+we have no certain confidence if a message is spam or not. We have some probability
+instead. To allow fuzzy rules weight, rspamd supports `dynamic weights`. Generally,
+it means that a rule may add a dynamic range from 0 to a defined weight in the metric.
+So far if we define symbol `BAYES_SPAM` with weight 5.0, then this rule can add
+a resulting symbol with weight from 0 to 5.0. To distribute values in the proper
+way, rspamd usually uses some sort of Sigma function to provide fair distribution curve.
+Nevertheless, the most of rspamd rules uses static weights with the exception of
+fuzzy rules.
+
+## Statistic
+
+Rspamd uses statistic algorithms to precise the final score of a message. Currently,
+the only algorithm defined is OSB-Bayes. You may find the concrete details of this
+algorithm in the following [paper](http://osbf-lua.luaforge.net/papers/osbf-eddc.pdf).
+Rspamd uses window size of 5 words in its classification. During classification procedure,
+rspamd split a message to a set of tokens.
+
+Tokens are separated by punctiation or space characters. Short tokens (less than 3 symbols) are ignored. For each token rspamd
+calculates two non-cryptographic hashes used subsequently as indices. All these tokens
+are stored in memory-mapped files called `statistic files` (or `statfiles`). Each statfile
+is a set of token chains, indexed by the first hash. A new token may be inserted to some
+chain, and if this chain is full then rspamd tries to expire less significant tokens to
+insert a new one. It is possible to obtain the current state of tokens by running
+
+ rspamc stat`
+
+command that asks controller for free and used tokens in each statfile.
+Please note that if a statfile is close to be completely filled then during subsequent
+learning you will loose existing data. Therefore, it is recommended to increase size for
+such statfiles.
+
diff --git a/doc/markdown/configuration/index.md b/doc/markdown/configuration/index.md
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/doc/markdown/configuration/index.md
diff --git a/doc/markdown/index.md b/doc/markdown/index.md
new file mode 100644
index 000000000..181dc956e
--- /dev/null
+++ b/doc/markdown/index.md
@@ -0,0 +1,22 @@
+# Rspamd documentation project
+
+## Introduction
+Rspamd is a fast and advanced spam filtering system. It is based on event-driven processing model
+allowing to work with multiple messages simultaneously without blocking anywhere during messages
+processing. Rspamd contains various modules shipped in the default distribution and permits to be
+extended with the own custom modules and rules written in [Lua](http://lua.org) programming language.
+Rspamd uses complex estimation system based on a set of rules, each of those rules has its own score and
+the final score of a message is defined by a sum of rules' scores that were true for that message. This approach
+is similar to other complex spam filtering systems, such as [SpamAssassin](http://spamassassin.apache.org).
+At the same time, rspamd uses fuzzy logic to process unknown messages. That includes fuzzy hashes and
+statistics module.
+
+## Table of Contents
+This document contains the basic documentation for rspamd spam filtering system. It is divided into the following
+parts:
+
+- [Architecture](architecture/) presents the architecture of rspamd and how spam filtering is performed
+- [Rspamd configuration](configuration/) describes principles of rspamd configuration
+- [Modules](modules/) chapter lists rspamd modules and defines their configuration attributes
+- [Workers](workers/) section describes workers that are implemented in the rspamd
+- [Lua API](lua/) explains how to extend rspamd with own lua modules \ No newline at end of file
diff --git a/doc/markdown/lua/index.md b/doc/markdown/lua/index.md
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/doc/markdown/lua/index.md
diff --git a/doc/markdown/modules/chartable.md b/doc/markdown/modules/chartable.md
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/doc/markdown/modules/chartable.md
diff --git a/doc/markdown/modules/dkim.md b/doc/markdown/modules/dkim.md
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/doc/markdown/modules/dkim.md
diff --git a/doc/markdown/modules/emails.md b/doc/markdown/modules/emails.md
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/doc/markdown/modules/emails.md
diff --git a/doc/markdown/modules/forged_recipients.md b/doc/markdown/modules/forged_recipients.md
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/doc/markdown/modules/forged_recipients.md
diff --git a/doc/markdown/modules/fuzzy_check.md b/doc/markdown/modules/fuzzy_check.md
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/doc/markdown/modules/fuzzy_check.md
diff --git a/doc/markdown/modules/index.md b/doc/markdown/modules/index.md
new file mode 100644
index 000000000..63c55ed2b
--- /dev/null
+++ b/doc/markdown/modules/index.md
@@ -0,0 +1,64 @@
+# Rspamd modules
+
+Rspamd ships with a set of modules. Some modules are written in C to speedup
+complex procedures while others are written in lua to reduce code size.
+Actually, new modules are encouraged to be written in lua and add the essential
+support to the Lua API itself. Truly speaking, lua modules are very close to
+C modules in terms of performance. However, lua modules can be written and loaded
+dynamically.
+
+## C Modules
+
+C modules provides core functionality of rspamd and are actually statically linked
+to the main rspamd code. C modules are defined in the `options` section of rspamd
+configuration. If no `filters` attribute is defined then all modules are disabled.
+The default configuration enables all modules explicitly:
+
+~~~nginx
+filters = "chartable,dkim,spf,surbl,regexp,fuzzy_check";
+~~~
+
+Here is the list of C modules available:
+
+- [regexp](regexp.md): the core module that allow to define regexp rules,
+rspamd internal functions and lua rules.
+- [surbl](surbl.md): this module extracts URLs from messages and check them against
+public DNS black lists to filter messages with malicious URLs.
+- [spf](spf.md): checks SPF records for messages processed.
+- [dkim](dkim.md): performs DKIM signatures checks.
+- [fuzzy_check](fuzzy_check.md): checks messages fuzzy hashes against public blacklists.
+- [chartable](chartable.md): checks character sets of text parts in messages.
+
+## Lua modules
+
+Lua modules are dynamically loaded on rspamd startup and are reloaded on rspamd
+reconfiguration. Should you want to write a lua module consult with the
+[Lua API documentation](../lua/). To define path to lua modules there is a special section
+named `modules` in rspamd:
+
+~~~nginx
+modules {
+ path = "/path/to/dir/";
+ path = "/path/to/module.lua";
+ path = "$PLUGINSDIR/lua";
+}
+~~~
+
+If a path is a directory then rspamd scans it for `*.lua" pattern and load all
+files matched.
+
+Here is the list of Lua modules shipped with rspamd:
+
+- [multimap](multimap.md) - a complex module that operates with different types
+of maps.
+- [rbl](rbl.md) - a plugin that checks messages against DNS blacklist based on
+either SMTP FROM addresses or on information from `Received` headers.
+- [emails](emails.md) - extract emails from a message and checks it against DNS
+blacklists.
+- [maillist](maillist.md) - determines the common mailing list signatures in a message.
+- [once_received](once_received.md) - detects messages with a single `Received` headers
+and performs some additional checks for such messages.
+- [phishing](phishing.md) - detects messages with phished URLs.
+- [ratelimit](ratelimit.md) - implements leaked bucket algorithm for ratelimiting and
+uses `redis` to store data.
+- [trie](trie.md) - uses suffix trie for extra-fast patterns lookup in messages. \ No newline at end of file
diff --git a/doc/markdown/modules/maillist.md b/doc/markdown/modules/maillist.md
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/doc/markdown/modules/maillist.md
diff --git a/doc/markdown/modules/multimap.md b/doc/markdown/modules/multimap.md
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/doc/markdown/modules/multimap.md
diff --git a/doc/markdown/modules/once_received.md b/doc/markdown/modules/once_received.md
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/doc/markdown/modules/once_received.md
diff --git a/doc/markdown/modules/phishing.md b/doc/markdown/modules/phishing.md
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/doc/markdown/modules/phishing.md
diff --git a/doc/markdown/modules/ratelimit.md b/doc/markdown/modules/ratelimit.md
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/doc/markdown/modules/ratelimit.md
diff --git a/doc/markdown/modules/rbl.md b/doc/markdown/modules/rbl.md
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/doc/markdown/modules/rbl.md
diff --git a/doc/markdown/modules/regexp.md b/doc/markdown/modules/regexp.md
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/doc/markdown/modules/regexp.md
diff --git a/doc/markdown/modules/spf.md b/doc/markdown/modules/spf.md
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/doc/markdown/modules/spf.md
diff --git a/doc/markdown/modules/surbl.md b/doc/markdown/modules/surbl.md
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/doc/markdown/modules/surbl.md
diff --git a/doc/markdown/modules/trie.md b/doc/markdown/modules/trie.md
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/doc/markdown/modules/trie.md
diff --git a/doc/markdown/workers/index.md b/doc/markdown/workers/index.md
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/doc/markdown/workers/index.md
diff --git a/doc/rspamc.1 b/doc/rspamc.1
index 3ca41ac98..73be9afb4 100644
--- a/doc/rspamc.1
+++ b/doc/rspamc.1
@@ -1,260 +1,212 @@
-.\" Automatically generated by Pod::Man 2.25 (Pod::Simple 3.16)
-.\"
-.\" Standard preamble:
-.\" ========================================================================
-.de Sp \" Vertical space (when we can't use .PP)
-.if t .sp .5v
-.if n .sp
-..
-.de Vb \" Begin verbatim text
-.ft CW
-.nf
-.ne \\$1
-..
-.de Ve \" End verbatim text
-.ft R
-.fi
-..
-.\" Set up some character translations and predefined strings. \*(-- will
-.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
-.\" double quote, and \*(R" will give a right double quote. \*(C+ will
-.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
-.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
-.\" nothing in troff, for use with C<>.
-.tr \(*W-
-.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
-.ie n \{\
-. ds -- \(*W-
-. ds PI pi
-. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
-. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
-. ds L" ""
-. ds R" ""
-. ds C` ""
-. ds C' ""
-'br\}
-.el\{\
-. ds -- \|\(em\|
-. ds PI \(*p
-. ds L" ``
-. ds R" ''
-'br\}
-.\"
-.\" Escape single quotes in literal strings from groff's Unicode transform.
-.ie \n(.g .ds Aq \(aq
-.el .ds Aq '
-.\"
-.\" If the F register is turned on, we'll generate index entries on stderr for
-.\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index
-.\" entries marked with X<> in POD. Of course, you'll have to process the
-.\" output yourself in some meaningful fashion.
-.ie \nF \{\
-. de IX
-. tm Index:\\$1\t\\n%\t"\\$2"
-..
-. nr % 0
-. rr F
-.\}
-.el \{\
-. de IX
-..
-.\}
-.\"
-.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
-.\" Fear. Run. Save yourself. No user-serviceable parts.
-. \" fudge factors for nroff and troff
-.if n \{\
-. ds #H 0
-. ds #V .8m
-. ds #F .3m
-. ds #[ \f1
-. ds #] \fP
-.\}
-.if t \{\
-. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
-. ds #V .6m
-. ds #F 0
-. ds #[ \&
-. ds #] \&
-.\}
-. \" simple accents for nroff and troff
-.if n \{\
-. ds ' \&
-. ds ` \&
-. ds ^ \&
-. ds , \&
-. ds ~ ~
-. ds /
-.\}
-.if t \{\
-. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
-. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
-. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
-. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
-. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
-. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
-.\}
-. \" troff and (daisy-wheel) nroff accents
-.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
-.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
-.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
-.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
-.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
-.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
-.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
-.ds ae a\h'-(\w'a'u*4/10)'e
-.ds Ae A\h'-(\w'A'u*4/10)'E
-. \" corrections for vroff
-.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
-.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
-. \" for low resolution devices (crt and lpr)
-.if \n(.H>23 .if \n(.V>19 \
-\{\
-. ds : e
-. ds 8 ss
-. ds o a
-. ds d- d\h'-1'\(ga
-. ds D- D\h'-1'\(hy
-. ds th \o'bp'
-. ds Th \o'LP'
-. ds ae ae
-. ds Ae AE
-.\}
-.rm #[ #] #H #V #F C
-.\" ========================================================================
-.\"
-.IX Title "rspamc 1"
-.TH rspamc 1 "2013-02-02" "rspamd-0.5.4" "Rspamd documentation"
-.\" For nroff, turn off justification. Always turn off hyphenation; it makes
-.\" way too many mistakes in technical documents.
-.if n .ad l
-.nh
-.SH "NAME"
-rspamc \- a simple client for rspamd spam filtering system
-.SH "SYNOPSIS"
-.IX Header "SYNOPSIS"
-rspamc [\fB\-h\fR \fIhost[:port]\fR] [\fB\-p\fR] [\fB\-v\fR] [\fB\-b\fR \fIbind_address\fR] [\fB\-u\fR \fIuser\fR]
-[\fB\-F\fR \fIfrom\fR] [\fB\-r\fR \fIrcpt\fR] [\fB\-d\fR \fIdeliver-to\fR]
-[\fB\-i\fR \fIip\fR] [\fB\-c\fR \fIclassifier\fR] [\fB\-w\fR \fIweight\fR]
-[\fB\-P\fR \fIpassword\fR] [\fB\-f\fR \fIflag\fR] [\fB\-t\fR \fItimeout\fR] [command] [file [file ...]]
+.TH RSPAMC 1 "" "Rspamd User Manual"
+.SH NAME
+.PP
+rspamc - rspamd command line client
+.SH SYNOPSIS
+.PP
+rspamc [\f[I]options\f[]] [\f[I]command\f[]] [\f[I]input-file\f[]]...
+.PP
+rspamc --help
+.SH DESCRIPTION
+.PP
+Rspamc is a simple client for checking messages using rspamd or for
+learning rspamd by messages.
+Rspamc supports the following commands:
+.IP \[bu] 2
+Scan commands:
+.RS 2
+.IP \[bu] 2
+\f[I]symbols\f[]: scan message and show symbols (default command)
+.RE
+.IP \[bu] 2
+Control commands
+.RS 2
+.IP \[bu] 2
+\f[I]learn_spam\f[]: learn message as spam
+.IP \[bu] 2
+\f[I]learn_ham\f[]: learn message as ham
+.IP \[bu] 2
+\f[I]fuzzy_add\f[]: add message to fuzzy storage (check \f[C]-f\f[] and
+\f[C]-w\f[] options for this command)
+.IP \[bu] 2
+\f[I]fuzzy_del\f[]: delete message from fuzzy storage (check \f[C]-f\f[]
+option for this command)
+.IP \[bu] 2
+\f[I]stat\f[]: show rspamd statistics
+.IP \[bu] 2
+\f[I]stat_reset\f[]: show and reset rspamd statistics (useful for
+graphs)
+.IP \[bu] 2
+\f[I]counters\f[]: display rspamd symbols statistics
+.IP \[bu] 2
+\f[I]uptime\f[]: show rspamd uptime
+.IP \[bu] 2
+\f[I]add_symbol\f[]: add or modify symbol settings in rspamd
+.IP \[bu] 2
+\f[I]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.
+.PP
+\f[C]Input\ files\f[] may be either regular file(s) or a directory to
+scan.
+If no files are specified rspamc reads from the standard input.
+Controller commands usually does not accept any input, however learn*
+and fuzzy* commands requires input.
+.SH OPTIONS
+.PP
+-h \f[I]host[:port]\f[], --connect=\f[I]host[:port]\f[] Specify host and
+port
+.PP
+-P \f[I]password\f[], --password=\f[I]password\f[] Specify control
+password
+.TP
+.B -c \f[I]name\f[], --classifier=\f[I]name\f[]
+Classifier to learn spam or ham (bayes is used by default)
+.RS
+.RE
+.TP
+.B -w \f[I]weight\f[], --weight=\f[I]weight\f[]
+Weight for fuzzy operations
+.RS
+.RE
+.TP
+.B -f \f[I]number\f[], --flag=\f[I]number\f[]
+Flag for fuzzy operations
+.RS
+.RE
+.TP
+.B -p, --pass
+Pass all filters
+.RS
+.RE
+.TP
+.B -v, --verbose
+More verbose output
+.RS
+.RE
+.TP
+.B -i \f[I]ip address\f[], --ip=\f[I]ip address\f[]
+Emulate that message was received from specified ip address
+.RS
+.RE
+.TP
+.B -u \f[I]username\f[], --user=\f[I]username\f[]
+Emulate that message was from specified user
+.RS
+.RE
+.TP
+.B -d \f[I]user\@domain\f[], --deliver=\f[I]user\@domain\f[]
+Emulate that message is delivered to specified user
+.RS
+.RE
+.TP
+.B -F \f[I]user\@domain\f[], --from=\f[I]user\@domain\f[]
+Emulate that message is from specified user
+.RS
+.RE
+.TP
+.B -r \f[I]user\@domain\f[], --rcpt=\f[I]user\@domain\f[]
+Emulate that message is for specified user
+.RS
+.RE
+.TP
+.B -t \f[I]seconds\f[], --timeout=\f[I]seconds\f[]
+Timeout for waiting for a reply
+.RS
+.RE
+.TP
+.B -b \f[I]host:port\f[], --bind=\f[I]host:port\f[]
+Bind to specified ip address
+.RS
+.RE
+.TP
+.B --commands
+List available commands
+.RS
+.RE
+.SH RETURN VALUE
+.PP
+On exit rspamc returns \f[C]0\f[] if operation was successfull and an
+error code otherwise.
+.SH EXAMPLES
.PP
-rspamc [\fB\-\-help\fR]
-.SH "DESCRIPTION"
-.IX Header "DESCRIPTION"
-\&\fBRspamc\fR is a simple client for checking messages using rspamd or for learning rspamd by messages.
-\&\fBRspamc\fR has several mandatory options for learning: \fIpassword\fR and \fIstatfile\fR.
-.SH "OPTIONS"
-.IX Header "OPTIONS"
-.IP "\fB\-h\fR \fIhost[:port]\fR, \fB\-\-connect\fR \fIhost[:port]\fR" 4
-.IX Item "-h host[:port], --connect host[:port]"
-Specify host and port for connecting to rspamd server. Default host is \fIlocalhost\fR and
-default port is \fI11333\fR for checking messages and \fI11334\fR for learning and statistic.
-Also it is possible to specify a unix socket for all operations (for example:
-\&\fBrspamc\fR \fB\-h\fR /path/to/soket)
-.IP "\fB\-b\fR \fIlocal_ip\fR, \fB\-\-bind\fR \fIlocal_ip\fR" 4
-.IX Item "-b local_ip, --bind local_ip"
-Specify explicit \s-1IP\s0 address to bind a client for operations.
-.IP "\fB\-u\fR \fIuser\fR, \fB\-\-user\fR \fIuser\fR" 4
-.IX Item "-u user, --user user"
-Specify username for connection with rspamd server.
-.IP "\fB\-F\fR \fIfrom_addr\fR, \fB\-\-from\fR \fIfrom_addr\fR" 4
-.IX Item "-F from_addr, --from from_addr"
-Specify \s-1SMTP\s0 \s-1FROM\s0 address for connection with rspamd server.
-.IP "\fB\-r\fR \fIrcpt_addr\fR, \fB\-\-rcpt\fR \fIrcpt_addr\fR" 4
-.IX Item "-r rcpt_addr, --rcpt rcpt_addr"
-Specify \s-1SMTP\s0 \s-1RCPT\s0 \s-1TO\s0 address for connection with rspamd server.
-.IP "\fB\-d\fR \fIdeliver_addr\fR, \fB\-\-deliver\fR \fIdeliver_addr\fR" 4
-.IX Item "-d deliver_addr, --deliver deliver_addr"
-Specify real delivery address for connection with rspamd server.
-.IP "\fB\-p\fR, \fB\-\-pass\-all\fR" 4
-.IX Item "-p, --pass-all"
-Pass all filters when checking messages. Ignored in case of learning.
-.IP "\fB\-v\fR, \fB\-\-verbose\fR" 4
-.IX Item "-v, --verbose"
-Be more verbose while displaying results. For example show descriptions of symbols.
-.IP "\fB\-P\fR \fIpassword\fR, \fB\-\-password\fR \fIpassword\fR" 4
-.IX Item "-P password, --password password"
-Specify controller's password. Mandatory option for learning.
-.IP "\fB\-c\fR \fIclassifier\fR, \fB\-\-classifier\fR \fIclassifier\fR" 4
-.IX Item "-c classifier, --classifier classifier"
-Specify classifier to learn message. Mandatory option for learning. Bayes classifier is used by default if this option is omitted.
-.IP "\fB\-i\fR \fIip\fR, \fB\-\-ip\fR \fIip\fR" 4
-.IX Item "-i ip, --ip ip"
-Add \s-1IP\s0 header when scanning message. Useful for checking messages and emulating that client comes from
-specific \s-1IP\s0 address.
-.IP "\fB\-w\fR \fIweight\fR, \fB\-\-weight\fR \fIweight\fR" 4
-.IX Item "-w weight, --weight weight"
-Weight of message for fuzzy operations.
-.IP "\fB\-f\fR \fIflag\fR, \fB\-\-flag\fR \fIflag\fR" 4
-.IX Item "-f flag, --flag flag"
-Flag of list for fuzzy operations.
-.IP "\fB\-t\fR \fItimeout\fR, \fB\-\-timeout\fR \fItimeout\fR" 4
-.IX Item "-t timeout, --timeout timeout"
-Timeout in seconds for all operations. Default value is 5 seconds.
-.SH "RETURN VALUE"
-.IX Header "RETURN VALUE"
-On exit \fBrspamc\fR returns 0 if operation was successfull and error code otherwise.
-.SH "EXAMPLES"
-.IX Header "EXAMPLES"
Check stdin:
-.PP
-.Vb 1
-\& rspamc < some_file
-.Ve
+.IP
+.nf
+\f[C]
+rspamc\ <\ some_file
+\f[]
+.fi
.PP
Check files:
-.PP
-.Vb 1
-\& rspamc symbols file1 file2 file3
-.Ve
+.IP
+.nf
+\f[C]
+rspamc\ symbols\ file1\ file2\ file3
+\f[]
+.fi
.PP
Learn files:
-.PP
-.Vb 1
-\& rspamc \-P pass learn_spam file1 file2 file3
-.Ve
+.IP
+.nf
+\f[C]
+rspamc\ -P\ pass\ learn_spam\ file1\ file2\ file3
+\f[]
+.fi
.PP
Add fuzzy hash to set 2:
-.PP
-.Vb 1
-\& rspamc \-P pass \-f 2 \-w 10 fuzzy_add file1 file2
-.Ve
+.IP
+.nf
+\f[C]
+rspamc\ -P\ pass\ -f\ 2\ -w\ 10\ fuzzy_add\ file1\ file2
+\f[]
+.fi
.PP
Delete fuzzy hash from other server:
-.PP
-.Vb 1
-\& rspamc \-P pass \-h hostname:11334 \-f 2 fuzzy_del file1 file2
-.Ve
+.IP
+.nf
+\f[C]
+rspamc\ -P\ pass\ -h\ hostname:11334\ -f\ 2\ fuzzy_del\ file1\ file2
+\f[]
+.fi
.PP
Get statistics:
-.PP
-.Vb 1
-\& rspamc stat
-.Ve
+.IP
+.nf
+\f[C]
+rspamc\ stat
+\f[]
+.fi
.PP
Get uptime:
+.IP
+.nf
+\f[C]
+rspamc\ uptime
+\f[]
+.fi
.PP
-.Vb 1
-\& rspamc uptime
-.Ve
-.PP
-Add custom rule's weight:
-.PP
-.Vb 1
-\& rspamc add_symbol test 1.5
-.Ve
-.PP
-Add custom action's weight:
+Add custom rule\[aq]s weight:
+.IP
+.nf
+\f[C]
+rspamc\ add_symbol\ test\ 1.5
+\f[]
+.fi
.PP
-.Vb 1
-\& rspamc add_action reject 7.1
-.Ve
-.SH "AUTHOR"
-.IX Header "AUTHOR"
-Vsevolod Stakhov <vsevolod@highsecure.ru>
-.SH "COPYRIGHT AND LICENSE"
-.IX Header "COPYRIGHT AND LICENSE"
-Copyright 2011\-2012 by Vsevolod Stakhov <vsevolod@highsecure.ru>.
+Add custom action\[aq]s weight:
+.IP
+.nf
+\f[C]
+rspamc\ add_action\ reject\ 7.1
+\f[]
+.fi
+.SH SEE ALSO
.PP
-This program is free software; you may redistribute it and/or modify it
-under the terms of \s-1BSD\s0 license.
+Rspamd documentation and source codes may be downloaded from
+<https://rspamd.com/>.
diff --git a/doc/rspamc.1.md b/doc/rspamc.1.md
new file mode 100644
index 000000000..531e0802d
--- /dev/null
+++ b/doc/rspamc.1.md
@@ -0,0 +1,134 @@
+% RSPAMC(1) Rspamd User Manual
+
+# NAME
+
+rspamc - rspamd command line client
+
+# SYNOPSIS
+
+rspamc [*options*] [*command*] [*input-file*]...
+
+rspamc --help
+
+# DESCRIPTION
+
+Rspamc is a simple client for checking messages using rspamd or for learning rspamd by messages.
+Rspamc supports the following commands:
+
+* Scan commands:
+ * *symbols*: scan message and show symbols (default command)
+* Control commands
+ * *learn_spam*: learn message as spam
+ * *learn_ham*: learn message as ham
+ * *fuzzy_add*: add message to fuzzy storage (check `-f` and `-w` options for this command)
+ * *fuzzy_del*: delete message from fuzzy storage (check `-f` option for this command)
+ * *stat*: show rspamd statistics
+ * *stat_reset*: show and reset rspamd statistics (useful for graphs)
+ * *counters*: display rspamd symbols statistics
+ * *uptime*: show rspamd uptime
+ * *add_symbol*: add or modify symbol settings in rspamd
+ * *add_action*: add or modify action settings
+
+Control commands that modifies rspamd state are considered as privileged and basically requires a password
+to be specified with `-P` option (see **OPTIONS**, below, for details).
+This depends on a controller's settings and is discussed in `rspamd-workers` page.
+
+`Input files` may be either regular file(s) or a directory to scan. If no files are specified rspamc reads
+from the standard input. Controller commands usually does not accept any input, however learn* and fuzzy* commands
+requires input.
+
+# OPTIONS
+
+-h *host[:port]*, \--connect=*host[:port]*
+ Specify host and port
+
+-P *password*, \--password=*password*
+ Specify control password
+
+-c *name*, \--classifier=*name*
+: Classifier to learn spam or ham (bayes is used by default)
+
+-w *weight*, \--weight=*weight*
+: Weight for fuzzy operations
+
+-f *number*, \--flag=*number*
+: Flag for fuzzy operations
+
+-p, \--pass
+: Pass all filters
+
+-v, \--verbose
+: More verbose output
+
+-i *ip address*, \--ip=*ip address*
+: Emulate that message was received from specified ip address
+
+-u *username*, \--user=*username*
+: Emulate that message was from specified user
+
+-d *user@domain*, \--deliver=*user@domain*
+: Emulate that message is delivered to specified user
+
+-F *user@domain*, \--from=*user@domain*
+: Emulate that message is from specified user
+
+-r *user@domain*, \--rcpt=*user@domain*
+: Emulate that message is for specified user
+
+-t *seconds*, \--timeout=*seconds*
+: Timeout for waiting for a reply
+
+-b *host:port*, \--bind=*host:port*
+: Bind to specified ip address
+
+\--commands
+: List available commands
+
+# RETURN VALUE
+
+On exit rspamc returns `0` if operation was successfull and an error code otherwise.
+
+# EXAMPLES
+
+Check stdin:
+
+ rspamc < some_file
+
+Check files:
+
+ rspamc symbols file1 file2 file3
+
+Learn files:
+
+ rspamc -P pass learn_spam file1 file2 file3
+
+Add fuzzy hash to set 2:
+
+ rspamc -P pass -f 2 -w 10 fuzzy_add file1 file2
+
+Delete fuzzy hash from other server:
+
+ rspamc -P pass -h hostname:11334 -f 2 fuzzy_del file1 file2
+
+Get statistics:
+
+ rspamc stat
+
+Get uptime:
+
+ rspamc uptime
+
+Add custom rule's weight:
+
+ rspamc add_symbol test 1.5
+
+Add custom action's weight:
+
+ rspamc add_action reject 7.1
+
+# SEE ALSO
+
+Rspamd documentation and source codes may be downloaded from
+<https://rspamd.com/>.
+
+[rspamd-workers]: https://rspamd.com/doc/rspamd-workers.html \ No newline at end of file
diff --git a/doc/rspamc.pod b/doc/rspamc.pod
deleted file mode 100644
index 73b43b649..000000000
--- a/doc/rspamc.pod
+++ /dev/null
@@ -1,138 +0,0 @@
-=head1 NAME
-
-rspamc - a simple client for rspamd spam filtering system
-
-=head1 SYNOPSIS
-
-rspamc [B<-h> I<host[:port]>] [B<-p>] [B<-v>] [B<-b> I<bind_address>] [B<-u> I<user>]
-[B<-F> I<from>] [B<-r> I<rcpt>] [B<-d> I<deliver-to>]
-[B<-i> I<ip>] [B<-c> I<classifier>] [B<-w> I<weight>]
-[B<-P> I<password>] [B<-f> I<flag>] [B<-t> I<timeout>] [command] [file [file ...]]
-
-rspamc [B<--help>]
-
-=head1 DESCRIPTION
-
-B<Rspamc> is a simple client for checking messages using rspamd or for learning rspamd by messages.
-B<Rspamc> has several mandatory options for learning: I<password> and I<statfile>.
-
-=head1 OPTIONS
-
-=over 4
-
-=item B<-h> I<host[:port]>, B<--connect> I<host[:port]>
-
-Specify host and port for connecting to rspamd server. Default host is I<localhost> and
-default port is I<11333> for checking messages and I<11334> for learning and statistic.
-Also it is possible to specify a unix socket for all operations (for example:
-B<rspamc> B<-h> /path/to/soket)
-
-=item B<-b> I<local_ip>, B<--bind> I<local_ip>
-
-Specify explicit IP address to bind a client for operations.
-
-=item B<-u> I<user>, B<--user> I<user>
-
-Specify username for connection with rspamd server.
-
-=item B<-F> I<from_addr>, B<--from> I<from_addr>
-
-Specify SMTP FROM address for connection with rspamd server.
-
-=item B<-r> I<rcpt_addr>, B<--rcpt> I<rcpt_addr>
-
-Specify SMTP RCPT TO address for connection with rspamd server.
-
-=item B<-d> I<deliver_addr>, B<--deliver> I<deliver_addr>
-
-Specify real delivery address for connection with rspamd server.
-
-=item B<-p>, B<--pass-all>
-
-Pass all filters when checking messages. Ignored in case of learning.
-
-=item B<-v>, B<--verbose>
-
-Be more verbose while displaying results. For example show descriptions of symbols.
-
-=item B<-P> I<password>, B<--password> I<password>
-
-Specify controller's password. Mandatory option for learning.
-
-=item B<-c> I<classifier>, B<--classifier> I<classifier>
-
-Specify classifier to learn message. Mandatory option for learning. Bayes classifier is used by default if this option is omitted.
-
-=item B<-i> I<ip>, B<--ip> I<ip>
-
-Add IP header when scanning message. Useful for checking messages and emulating that client comes from
-specific IP address.
-
-=item B<-w> I<weight>, B<--weight> I<weight>
-
-Weight of message for fuzzy operations.
-
-=item B<-f> I<flag>, B<--flag> I<flag>
-
-Flag of list for fuzzy operations.
-
-=item B<-t> I<timeout>, B<--timeout> I<timeout>
-
-Timeout in seconds for all operations. Default value is 5 seconds.
-
-=back
-
-=head1 RETURN VALUE
-
-On exit B<rspamc> returns 0 if operation was successfull and error code otherwise.
-
-=head1 EXAMPLES
-
-Check stdin:
-
- rspamc < some_file
-
-Check files:
-
- rspamc symbols file1 file2 file3
-
-Learn files:
-
- rspamc -P pass learn_spam file1 file2 file3
-
-Add fuzzy hash to set 2:
-
- rspamc -P pass -f 2 -w 10 fuzzy_add file1 file2
-
-Delete fuzzy hash from other server:
-
- rspamc -P pass -h hostname:11334 -f 2 fuzzy_del file1 file2
-
-Get statistics:
-
- rspamc stat
-
-Get uptime:
-
- rspamc uptime
-
-Add custom rule's weight:
-
- rspamc add_symbol test 1.5
-
-Add custom action's weight:
-
- rspamc add_action reject 7.1
-
-=head1 AUTHOR
-
-Vsevolod Stakhov <vsevolod@highsecure.ru>
-
-=head1 COPYRIGHT AND LICENSE
-
-Copyright 2011-2012 by Vsevolod Stakhov <vsevolod@highsecure.ru>.
-
-This program is free software; you may redistribute it and/or modify it
-under the terms of BSD license.
-
-=cut
diff --git a/doc/rspamd.8 b/doc/rspamd.8
index c34e553fd..37650ebaa 100644
--- a/doc/rspamd.8
+++ b/doc/rspamd.8
@@ -1,190 +1,138 @@
-.\" Automatically generated by Pod::Man 2.25 (Pod::Simple 3.16)
-.\"
-.\" Standard preamble:
-.\" ========================================================================
-.de Sp \" Vertical space (when we can't use .PP)
-.if t .sp .5v
-.if n .sp
-..
-.de Vb \" Begin verbatim text
-.ft CW
+.TH RSPAMD 8 "" "Rspamd User Manual"
+.SH NAME
+.PP
+rspamd - main daemon for rapid spam filtering system
+.SH SYNOPSIS
+.PP
+rspamd [\f[I]options\f[]]...
+.PP
+rspamd --help
+.SH DESCRIPTION
+.PP
+Rspamd filtering system is designed to be fast, modular and easily
+scalable system.
+Rspamd core is written in \f[C]C\f[] language using event driven
+processing model.
+Plugins for rspamd can be written in \f[C]Lua\f[] programming language.
+Rspamd is designed to process connections completely asynchronous and do
+not block anywhere in code.
+.SH OPTIONS
+.TP
+.B -t, --config-test
+Perform config test and exit
+.RS
+.RE
+.TP
+.B -f, --no-fork
+Do not daemonize main process
+.RS
+.RE
+.TP
+.B -c \f[I]path\f[], --config=\f[I]path\f[]
+Specify config file(s)
+.RS
+.RE
+.TP
+.B -u \f[I]username\f[], --user=\f[I]username\f[]
+User to run rspamd as
+.RS
+.RE
+.TP
+.B -g \f[I]groupname\f[], --group=\f[I]groupname\f[]
+Group to run rspamd as
+.RS
+.RE
+.TP
+.B -p \f[I]path\f[], --pid=\f[I]path\f[]
+Path to pidfile
+.RS
+.RE
+.TP
+.B -C, --dump-cache
+Dump symbols cache stats and exit
+.RS
+.RE
+.TP
+.B -d, --debug
+Force debug output
+.RS
+.RE
+.TP
+.B -i, --insecure
+Ignore running workers as privileged users (insecure)
+.RS
+.RE
+.TP
+.B --test-lua=\f[I]path\f[]
+Specify lua file(s) to test
+.RS
+.RE
+.TP
+.B --sign-config=\f[I]path\f[]
+Specify config file(s) to sign
+.RS
+.RE
+.TP
+.B --private-key=\f[I]path\f[]
+Specify private key to sign
+.RS
+.RE
+.TP
+.B --convert-config=\f[I]path\f[]
+Convert configuration to UCL
+.RS
+.RE
+.SH EXAMPLES
+.PP
+Run rspamd daemon with default configuration:
+.IP
.nf
-.ne \\$1
-..
-.de Ve \" End verbatim text
-.ft R
+\f[C]
+rspamd
+\f[]
.fi
-..
-.\" Set up some character translations and predefined strings. \*(-- will
-.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
-.\" double quote, and \*(R" will give a right double quote. \*(C+ will
-.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
-.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
-.\" nothing in troff, for use with C<>.
-.tr \(*W-
-.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
-.ie n \{\
-. ds -- \(*W-
-. ds PI pi
-. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
-. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
-. ds L" ""
-. ds R" ""
-. ds C` ""
-. ds C' ""
-'br\}
-.el\{\
-. ds -- \|\(em\|
-. ds PI \(*p
-. ds L" ``
-. ds R" ''
-'br\}
-.\"
-.\" Escape single quotes in literal strings from groff's Unicode transform.
-.ie \n(.g .ds Aq \(aq
-.el .ds Aq '
-.\"
-.\" If the F register is turned on, we'll generate index entries on stderr for
-.\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index
-.\" entries marked with X<> in POD. Of course, you'll have to process the
-.\" output yourself in some meaningful fashion.
-.ie \nF \{\
-. de IX
-. tm Index:\\$1\t\\n%\t"\\$2"
-..
-. nr % 0
-. rr F
-.\}
-.el \{\
-. de IX
-..
-.\}
-.\"
-.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
-.\" Fear. Run. Save yourself. No user-serviceable parts.
-. \" fudge factors for nroff and troff
-.if n \{\
-. ds #H 0
-. ds #V .8m
-. ds #F .3m
-. ds #[ \f1
-. ds #] \fP
-.\}
-.if t \{\
-. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
-. ds #V .6m
-. ds #F 0
-. ds #[ \&
-. ds #] \&
-.\}
-. \" simple accents for nroff and troff
-.if n \{\
-. ds ' \&
-. ds ` \&
-. ds ^ \&
-. ds , \&
-. ds ~ ~
-. ds /
-.\}
-.if t \{\
-. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
-. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
-. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
-. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
-. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
-. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
-.\}
-. \" troff and (daisy-wheel) nroff accents
-.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
-.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
-.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
-.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
-.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
-.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
-.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
-.ds ae a\h'-(\w'a'u*4/10)'e
-.ds Ae A\h'-(\w'A'u*4/10)'E
-. \" corrections for vroff
-.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
-.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
-. \" for low resolution devices (crt and lpr)
-.if \n(.H>23 .if \n(.V>19 \
-\{\
-. ds : e
-. ds 8 ss
-. ds o a
-. ds d- d\h'-1'\(ga
-. ds D- D\h'-1'\(hy
-. ds th \o'bp'
-. ds Th \o'LP'
-. ds ae ae
-. ds Ae AE
-.\}
-.rm #[ #] #H #V #F C
-.\" ========================================================================
-.\"
-.IX Title "rspamd 8"
-.TH rspamd 8 "2013-02-02" "rspamd-0.5.4" "Rspamd documentation"
-.\" For nroff, turn off justification. Always turn off hyphenation; it makes
-.\" way too many mistakes in technical documents.
-.if n .ad l
-.nh
-.SH "NAME"
-rspamd \- main daemon for rspamd spam filtering system
-.SH "SYNOPSIS"
-.IX Header "SYNOPSIS"
-rspamd [\fB\-c\fR \fIconfig_file\fR] [\fB\-f\fR]
-[\fB\-u\fR \fIuser\fR] [\fB\-g\fR \fIgroup\fR] [\fB\-p\fR \fIpidfile\fR]
-[\fB\-t\fR] [\fB\-d\fR]
.PP
-rspamd [\fB\-\-help\fR]
+Run rspamd in foreground with custom configuration:
+.IP
+.nf
+\f[C]
+rspamd\ -f\ -c\ ~/rspamd.conf
+\f[]
+.fi
.PP
-rspamd [\fB\-t\fR]
+Run rspamd specifying user and group:
+.IP
+.nf
+\f[C]
+rspamd\ -u\ rspamd\ -g\ rspamd\ -c\ /etc/rspamd/rspamd.conf
+\f[]
+.fi
.PP
-rspamd [\fB\-C\fR]
-.SH "DESCRIPTION"
-.IX Header "DESCRIPTION"
-\&\fBRspamd\fR filtering system is designed to be fast, modular and easily extendable system.
-\&\fBRspamd\fR core is written in C language using event driven paradigma.
-Plugins for \fBrspamd\fR can be written in lua.
-\&\fBRspamd\fR is designed to process connections completely asynchronous and do not block anywhere in code.
-.SH "OPTIONS"
-.IX Header "OPTIONS"
-.IP "\fB\-c\fR \fIconfig_file\fR, \fB\-\-config\fR \fIconfig_file\fR" 4
-.IX Item "-c config_file, --config config_file"
-Specify the path where rspamd config is placed. Default is rspamd.xml.
-.IP "\fB\-u\fR \fIuser\fR, \fB\-\-user\fR \fIuser\fR" 4
-.IX Item "-u user, --user user"
-Specify user rspamd run as. It is possible only when rspamd is launched by super-user as it
-calls \fIsetuid\fR\|(2) after spawning workers.
-.IP "\fB\-g\fR \fIgroup\fR, \fB\-\-group\fR \fIgroup\fR" 4
-.IX Item "-g group, --group group"
-Specify group rspamd run as.
-.IP "\fB\-p\fR \fIpidfile\fR, \fB\-\-pidfile\fR \fIpidfile\fR" 4
-.IX Item "-p pidfile, --pidfile pidfile"
-Path to pid file where rspamd pid would be stored. Directory containing pidfile must be
-writeable by \fBrspamd\fR.
-.IP "\fB\-f\fR, \fB\-\-no\-fork\fR" 4
-.IX Item "-f, --no-fork"
-Do not daemonize after launch. Usable for debugging purposes.
-.IP "\fB\-t\fR, \fB\-\-config\-test\fR" 4
-.IX Item "-t, --config-test"
-Just perform test of configuration. Return zero exit code when configuration is \s-1OK\s0.
-.IP "\fB\-C\fR, \fB\-\-counters\fR" 4
-.IX Item "-C, --counters"
-Show counters for all symbols. Usable when symbols cache is saved.
-.IP "\fB\-d\fR, \fB\-\-debug\fR" 4
-.IX Item "-d, --debug"
-Turn on debugging mode in logging.
-.SH "RETURN VALUE"
-.IX Header "RETURN VALUE"
-On exit \fBrspamd\fR returns 0 if operation was successfull and error code otherwise.
-.SH "AUTHOR"
-.IX Header "AUTHOR"
-Vsevolod Stakhov <vsevolod@highsecure.ru>
-.SH "COPYRIGHT AND LICENSE"
-.IX Header "COPYRIGHT AND LICENSE"
-Copyright 2011 by Vsevolod Stakhov <vsevolod@highsecure.ru>.
+Test lua scripts using rspamd API:
+.IP
+.nf
+\f[C]
+rspamd\ --test-lua=~/test1.lua\ --test-lua=~/test2.lua
+\f[]
+.fi
+.PP
+Sign config files for \f[C].includes\f[] macro:
+.IP
+.nf
+\f[C]
+rspamd\ --private-key=sign.key\ --sign-config=rspamd.conf
+\f[]
+.fi
+.PP
+Convert old \f[C]XML\f[] config to the \f[C]UCL\f[] format (since
+0.6.0):
+.IP
+.nf
+\f[C]
+rspamd\ -c\ /etc/rspamd.xml\ --convert-config=/etc/rspamd/rspamd.conf
+\f[]
+.fi
+.SH SEE ALSO
.PP
-This program is free software; you may redistribute it and/or modify it
-under the terms of \s-1BSD\s0 license.
+Rspamd documentation and source codes may be downloaded from
+<https://rspamd.com/>.
diff --git a/doc/rspamd.8.md b/doc/rspamd.8.md
new file mode 100644
index 000000000..2e6d60b48
--- /dev/null
+++ b/doc/rspamd.8.md
@@ -0,0 +1,90 @@
+% RSPAMD(8) Rspamd User Manual
+
+# NAME
+
+rspamd - main daemon for rapid spam filtering system
+
+# SYNOPSIS
+
+rspamd [*options*]...
+
+rspamd --help
+
+# DESCRIPTION
+
+Rspamd filtering system is designed to be fast, modular and easily scalable system.
+Rspamd core is written in `C` language using event driven processing model.
+Plugins for rspamd can be written in `Lua` programming language.
+Rspamd is designed to process connections completely asynchronous and do not block anywhere in code.
+
+# OPTIONS
+
+-t, \--config-test
+: Perform config test and exit
+
+-f, \--no-fork
+: Do not daemonize main process
+
+-c *path*, \--config=*path*
+: Specify config file(s)
+
+-u *username*, \--user=*username*
+: User to run rspamd as
+
+-g *groupname*, \--group=*groupname*
+: Group to run rspamd as
+
+-p *path*, \--pid=*path*
+: Path to pidfile
+
+-C, \--dump-cache
+: Dump symbols cache stats and exit
+
+-d, \--debug
+: Force debug output
+
+-i, \--insecure
+: Ignore running workers as privileged users (insecure)
+
+\--test-lua=*path*
+: Specify lua file(s) to test
+
+\--sign-config=*path*
+: Specify config file(s) to sign
+
+\--private-key=*path*
+: Specify private key to sign
+
+\--convert-config=*path*
+: Convert configuration to UCL
+
+# EXAMPLES
+
+Run rspamd daemon with default configuration:
+
+ rspamd
+
+Run rspamd in foreground with custom configuration:
+
+ rspamd -f -c ~/rspamd.conf
+
+Run rspamd specifying user and group:
+
+ rspamd -u rspamd -g rspamd -c /etc/rspamd/rspamd.conf
+
+Test lua scripts using rspamd API:
+
+ rspamd --test-lua=~/test1.lua --test-lua=~/test2.lua
+
+Sign config files for `.includes` macro:
+
+ rspamd --private-key=sign.key --sign-config=rspamd.conf
+
+Convert old `XML` config to the `UCL` format (since 0.6.0):
+
+ rspamd -c /etc/rspamd.xml --convert-config=/etc/rspamd/rspamd.conf
+
+# SEE ALSO
+
+Rspamd documentation and source codes may be downloaded from
+<https://rspamd.com/>. \ No newline at end of file
diff --git a/doc/rspamd.pod b/doc/rspamd.pod
deleted file mode 100644
index 0e03d5c82..000000000
--- a/doc/rspamd.pod
+++ /dev/null
@@ -1,79 +0,0 @@
-=head1 NAME
-
-rspamd - main daemon for rspamd spam filtering system
-
-=head1 SYNOPSIS
-
-rspamd [B<-c> I<config_file>] [B<-f>]
-[B<-u> I<user>] [B<-g> I<group>] [B<-p> I<pidfile>]
-[B<-t>] [B<-d>]
-
-rspamd [B<--help>]
-
-rspamd [B<-t>]
-
-rspamd [B<-C>]
-
-=head1 DESCRIPTION
-
-B<Rspamd> filtering system is designed to be fast, modular and easily extendable system.
-B<Rspamd> core is written in C language using event driven paradigma.
-Plugins for B<rspamd> can be written in lua.
-B<Rspamd> is designed to process connections completely asynchronous and do not block anywhere in code.
-
-=head1 OPTIONS
-
-=over 4
-
-=item B<-c> I<config_file>, B<--config> I<config_file>
-
-Specify the path where rspamd config is placed. Default is rspamd.xml.
-
-=item B<-u> I<user>, B<--user> I<user>
-
-Specify user rspamd run as. It is possible only when rspamd is launched by super-user as it
-calls setuid(2) after spawning workers.
-
-=item B<-g> I<group>, B<--group> I<group>
-
-Specify group rspamd run as.
-
-=item B<-p> I<pidfile>, B<--pidfile> I<pidfile>
-
-Path to pid file where rspamd pid would be stored. Directory containing pidfile must be
-writeable by B<rspamd>.
-
-=item B<-f>, B<--no-fork>
-
-Do not daemonize after launch. Usable for debugging purposes.
-
-=item B<-t>, B<--config-test>
-
-Just perform test of configuration. Return zero exit code when configuration is OK.
-
-=item B<-C>, B<--counters>
-
-Show counters for all symbols. Usable when symbols cache is saved.
-
-=item B<-d>, B<--debug>
-
-Turn on debugging mode in logging.
-
-=back
-
-=head1 RETURN VALUE
-
-On exit B<rspamd> returns 0 if operation was successfull and error code otherwise.
-
-=head1 AUTHOR
-
-Vsevolod Stakhov <vsevolod@highsecure.ru>
-
-=head1 COPYRIGHT AND LICENSE
-
-Copyright 2011 by Vsevolod Stakhov <vsevolod@highsecure.ru>.
-
-This program is free software; you may redistribute it and/or modify it
-under the terms of BSD license.
-
-=cut \ No newline at end of file
diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt
index 51e8b218a..b236be14a 100644
--- a/lib/CMakeLists.txt
+++ b/lib/CMakeLists.txt
@@ -70,10 +70,9 @@ IF(CMAKE_COMPILER_IS_GNUCC)
SET_TARGET_PROPERTIES(rspamd-util PROPERTIES COMPILE_FLAGS "-fno-strict-aliasing")
ENDIF(CMAKE_COMPILER_IS_GNUCC)
-TARGET_LINK_LIBRARIES(rspamd-util ${CMAKE_REQUIRED_LIBRARIES})
+TARGET_LINK_LIBRARIES(rspamd-util ${RSPAMD_REQUIRED_LIBRARIES})
TARGET_LINK_LIBRARIES(rspamd-util pcre)
TARGET_LINK_LIBRARIES(rspamd-util rspamd-ucl)
-TARGET_LINK_LIBRARIES(rspamd-util ${GLIB2_LIBRARIES})
TARGET_LINK_LIBRARIES(rspamd-util event)
IF(NOT DEBIAN_BUILD)
@@ -101,16 +100,16 @@ IF(NOT DEBIAN_BUILD)
SET_TARGET_PROPERTIES(rspamdclient_static PROPERTIES COMPILE_FLAGS "-fno-strict-aliasing")
ENDIF(CMAKE_COMPILER_IS_GNUCC)
TARGET_LINK_LIBRARIES(rspamdclient rspamd-util)
- TARGET_LINK_LIBRARIES(rspamdclient_static ${CMAKE_REQUIRED_LIBRARIES})
- TARGET_LINK_LIBRARIES(rspamdclient_static ${GLIB2_LIBRARIES})
+ TARGET_LINK_LIBRARIES(rspamdclient ${RSPAMD_REQUIRED_LIBRARIES})
+ TARGET_LINK_LIBRARIES(rspamdclient_static rspamd-util)
+ TARGET_LINK_LIBRARIES(rspamdclient_static ${RSPAMD_REQUIRED_LIBRARIES})
ELSE(NOT DEBIAN_BUILD)
ADD_LIBRARY(rspamdclient STATIC ${LIBRSPAMDCLIENTSRC})
IF(CMAKE_COMPILER_IS_GNUCC)
SET_TARGET_PROPERTIES(rspamdclient PROPERTIES COMPILE_FLAGS "-fno-strict-aliasing")
ENDIF(CMAKE_COMPILER_IS_GNUCC)
TARGET_LINK_LIBRARIES(rspamdclient rspamd-util)
- TARGET_LINK_LIBRARIES(rspamdclient ${CMAKE_REQUIRED_LIBRARIES})
- TARGET_LINK_LIBRARIES(rspamdclient ${GLIB2_LIBRARIES})
+ TARGET_LINK_LIBRARIES(rspamdclient ${RSPAMD_REQUIRED_LIBRARIES})
ENDIF(NOT DEBIAN_BUILD)
IF(NOT DEBIAN_BUILD)
@@ -159,9 +158,6 @@ ENDIF(CMAKE_COMPILER_IS_GNUCC)
IF(WITH_DB)
TARGET_LINK_LIBRARIES(rspamd-server db)
ENDIF(WITH_DB)
-IF(SQLITE_LIBRARIES)
- TARGET_LINK_LIBRARIES(rspamd-server ${SQLITE_LIBRARIES})
-ENDIF(SQLITE_LIBRARIES)
IF(OPENSSL_FOUND)
TARGET_LINK_LIBRARIES(rspamd-server ${OPENSSL_LIBRARIES})
@@ -186,12 +182,6 @@ IF(CMAKE_COMPILER_IS_GNUCC)
SET_TARGET_PROPERTIES(rspamd-mime PROPERTIES COMPILE_FLAGS "-DRSPAMD_LIB -fno-strict-aliasing")
ENDIF(CMAKE_COMPILER_IS_GNUCC)
-IF(GMIME24)
- TARGET_LINK_LIBRARIES(rspamd-mime ${GMIME24_LIBRARIES})
-ELSE(GMIME24)
- TARGET_LINK_LIBRARIES(rspamd-mime ${GMIME2_LIBRARIES})
-ENDIF(GMIME24)
-
IF(NO_SHARED MATCHES "OFF")
INSTALL(TARGETS rspamd-mime
LIBRARY DESTINATION ${LIBDIR}
diff --git a/perl/MANIFEST b/perl/MANIFEST
deleted file mode 100644
index 49ff80978..000000000
--- a/perl/MANIFEST
+++ /dev/null
@@ -1,3 +0,0 @@
-Makefile.PL
-MANIFEST
-lib/Mail/Rspamd/Client.pm
diff --git a/perl/Makefile.PL.in b/perl/Makefile.PL.in
deleted file mode 100644
index 625e69310..000000000
--- a/perl/Makefile.PL.in
+++ /dev/null
@@ -1,11 +0,0 @@
-use ExtUtils::MakeMaker;
-WriteMakefile(
- AUTHOR => 'Vsevolod Stakhov <vsevolod@highsecure.ru>',
- VERSION_FROM => 'lib/Mail/Rspamd/Client.pm', # finds $VERSION
- PREREQ_PM => {
- "IO::String" => 0,
- "Term::ReadKey" => 0,
- "XML::Parser" => 0,
- "IO::Socket" => 0,
- },
- );
diff --git a/perl/lib/Mail/Rspamd/Client.pm b/perl/lib/Mail/Rspamd/Client.pm
deleted file mode 100644
index 9353c2468..000000000
--- a/perl/lib/Mail/Rspamd/Client.pm
+++ /dev/null
@@ -1,1390 +0,0 @@
-
-=head1 NAME
-
-Mail::Rspamd::Client - Client for rspamd Protocol
-
-
-=head1 SYNOPSIS
-
- my $client = new Mail::Rspamd::Client($config);
-
- if ($client->ping()) {
- $self->{error} = "Ping is ok\n";
- }
-
- my $result = $client->check($testmsg);
-
- if ($result->{'default'}->{isspam} eq 'True') {
- do something with spam message here
- }
-
-=head1 DESCRIPTION
-
-Mail::Rspamd::Client is a module that provides a perl implementation for
-the spamd protocol.
-
-=cut
-
-package Mail::Rspamd::Client;
-
-use IO::Socket;
-use Carp;
-
-use vars qw($VERSION);
-$VERSION = "1.02";
-
-my $EOL = "\015\012";
-my $BLANK = $EOL x 2;
-my $PROTOVERSION = 'RSPAMC/1.2';
-
-=head1 PUBLIC METHODS
-
-=head2 new
-
-public class (Mail::Rspamd::Client) new (\% $args)
-
-Description:
-This method creates a new Mail::Rspamd::Client object.
-
-=cut
-
-sub new {
- my ($class, $args) = @_;
-
- $class = ref($class) || $class;
-
- my $self = {};
-
- # with a sockets_path set then it makes no sense to set host and port
- if ($args->{hosts}) {
- $self->{hosts} = $args->{hosts};
- $self->{alive_hosts} = $self->{hosts};
- }
-
- if ($args->{username}) {
- $self->{username} = $args->{username};
- }
- if ($args->{ip}) {
- $self->{ip} = $args->{ip};
- }
- if ($args->{from}) {
- $self->{from} = $args->{from};
- }
- if ($args->{subject}) {
- $self->{subject} = $args->{subject};
- }
- if ($args->{rcpt}) {
- $self->{rcpt} = $args->{rcpt};
- }
- if ($args->{deliver_to}) {
- $self->{deliver_to} = $args->{deliver_to};
- }
- if ($args->{timeout}) {
- $self->{timeout} = $args->{timeout};
- }
- else {
- $self->{timeout} = 5;
- }
- if ($args->{password}) {
- $self->{password} = $args->{password};
- }
- if ($args->{statfile}) {
- $self->{statfile} = $args->{statfile};
- }
- if ($args->{weight}) {
- $self->{weight} = $args->{weight};
- }
- else {
- $self->{weight} = 1;
- }
- if ($args->{pass_all}) {
- $self->{pass_all} = 1;
- }
- if ($args->{imap_search}) {
- $self->{imap_search} = $args->{imap_search};
- }
- else {
- $self->{imap_search} = 'ALL';
- }
-
- if ($args->{command}) {
- if ($args->{command} =~ /(SYMBOLS|PROCESS|CHECK|URLS|EMAILS)/i) {
- $self->{'command'} = $1;
- $self->{'control'} = 0;
- }
- elsif ($args->{command} =~ /(STAT|LEARN|SHUTDOWN|RELOAD|UPTIME|COUNTERS|FUZZY_ADD|FUZZY_DEL|WEIGHTS)/i) {
- $self->{'command'} = $1;
- $self->{'control'} = 1;
- }
- }
-
- $self->{error} = "";
-
- bless($self, $class);
-
- $self;
-}
-
-
-sub make_ssl_socket {
- my ($host, $port) = @_;
-
- eval {
- require IO::Socket::SSL;
- IO::Socket::SSL->import(LIST);
- } or croak "IO::Socket::SSL required for imaps";
-
- return IO::Socket::SSL->new("$host:$port");
-}
-
-
-
-=head2 process_item
-
-public instance (\%) process_item (String $item)
-
-Description:
-Do specified command for a single file, path or IMAP folder
-
-The return value is a hash reference containing results of each command for each server from cluster
-
-=cut
-
-sub process_item {
- my $self = shift;
- my $item = shift;
- my $cb = shift;
-
- if (defined ($item)) {
- if ($item =~ qr|^imap(s?):user:([^:]+):password:([^:]*):host:([^:]+):mbox:(.+)$|) {
- return $self->_process_imap ($1, $2, $3, $4, $5, $cb);
- }
- elsif (-f $item) {
- return $self->_process_file ($item, $cb);
- }
- elsif (-d $item) {
- return $self->_process_directory ($item, $cb);
- }
- else {
- warn "urecognized argument: $item";
- }
- }
- undef;
-}
-
-=head2 process_path
-
-public instance (\%) process_path ()
-
-Description:
-Do specified command for each file in path or message in IMAP folder
-
-The return value is a hash reference containing results of each command for each server from cluster
-
-=cut
-sub process_path {
- my $self = shift;
- my $cb = shift;
- my %res;
-
- foreach (@_) {
- $res{$_} = $self->process_item($_, $cb);
- }
-
- return \%res;
-}
-
-=head2 do_all_cmd
-
-public instance (\%) do_all_cmd (String $msg)
-
-Description:
-This method makes a call to the the whole rspamd cluster and call specified command
-(in $self->{command}).
-
-The return value is a hash reference containing results of each command for each server from cluster
-
-=cut
-
-sub do_all_cmd {
- my ($self, $input) = @_;
-
- my %res;
-
- if (!$self->{'hosts'} || scalar (@{ $self->{'hosts'} }) == 0) {
- $res{'error'} = 'Hosts list is empty';
- $res{'error_code'} = 404;
- }
- else {
- foreach my $hostdef (@{ $self->{'hosts'} }) {
- $self->_clear_errors();
-
- my $remote = $self->_create_connection($hostdef);
-
- if (! $remote) {
- $res{$hostdef}->{error_code} = 404;
- $res{$hostdef}->{error} = "Cannot connect to $hostdef";
- }
- else {
- if ($self->{'control'}) {
- $res{$hostdef} = $self->_do_control_command ($remote, $input);
- }
- else {
- $res{$hostdef} = $self->_do_rspamc_command ($remote, $input);
- }
- }
- }
- }
-
- return \%res;
-}
-
-=head2 do_cmd
-
-public instance (\%) do_cmd (String $msg)
-
-Description:
-This method makes a call to a single rspamd server from a cluster
-(in $self->{command}).
-
-The return value is a hash reference containing results of each command for each server from cluster
-
-=cut
-
-sub do_cmd {
- my ($self, $input) = @_;
-
- my $res;
-
- if (!$self->{'hosts'} || scalar (@{ $self->{'hosts'} }) == 0) {
- $res->{'error'} = 'Hosts list is empty';
- $res->{'error_code'} = 404;
- }
- else {
- $self->_clear_errors();
-
- my $remote = $self->_create_connection();
-
- if (! $remote) {
- $res->{error_code} = 404;
- $res->{error} = "Cannot connect to " . $remote;
- }
- else {
- if ($self->{'control'}) {
- $res = $self->_do_control_command ($remote, $input);
- }
- else {
- $res = $self->_do_rspamc_command ($remote, $input);
- }
- }
- }
-
- return $res;
-}
-
-
-=head2 check
-
-public instance (\%) check (String $msg)
-
-Description:
-This method makes a call to the spamd server and depending on the value of
-C<$is_check_p> either calls PROCESS or CHECK.
-
-The return value is a hash reference containing metrics indexed by name. Each metric
-is hash that contains data:
-
-=over
-=item *
-isspam
-
-=item *
-score
-
-=item *
-threshold
-
-=item *
-symbols - array of symbols
-
-=back
-
-=cut
-
-sub check {
- my ($self, $msg) = @_;
-
- $self->{command} = 'CHECK';
- $self->{control} = 0;
-
- return $self->do_cmd ($msg);
-}
-
-=head2 symbols
-
-public instance (\%) symbols (String $msg)
-
-Description:
-This method makes a call to the spamd server
-
-The return value is a hash reference containing metrics indexed by name. Each metric
-is hash that contains data:
-
-=over
-=item *
-isspam
-
-=item *
-score
-
-=item *
-threshold
-
-=item *
-symbols - array of symbols
-
-=back
-
-=cut
-
-sub symbols {
- my ($self, $msg) = @_;
-
- $self->{command} = 'SYMBOLS';
- $self->{control} = 0;
-
- return $self->do_cmd ($msg);
-}
-
-=head2 process
-
-public instance (\%) process (String $msg)
-
-Description:
-This method makes a call to the spamd server
-
-The return value is a hash reference containing metrics indexed by name. Each metric
-is hash that contains data:
-
-=over
-=item *
-isspam
-
-=item *
-score
-
-=item *
-threshold
-
-=item *
-symbols - array of symbols
-
-=back
-
-=cut
-sub process {
- my ($self, $msg) = @_;
-
- $self->{command} = 'PROCESS';
- $self->{control} = 0;
-
- return $self->do_cmd ($msg);
-}
-
-=head2 urls
-
-public instance (\%) urls (String $msg)
-
-Description:
-This method makes a call to the spamd server
-
-The return value is a hash reference containing metrics indexed by name. Each metric
-is hash that contains data:
-
-urls - list of all urls in message
-=cut
-sub urls {
- my ($self, $msg) = @_;
-
- $self->{command} = 'URLS';
- $self->{control} = 0;
-
- return $self->do_cmd ($msg);
-}
-
-
-=head2 learn
-
-public instance (\%) learn (String $msg)
-
-Description:
-This method makes a call to the spamd learning a statfile with message.
-
-=cut
-
-sub learn {
- my ($self, $msg) = @_;
-
- $self->{command} = 'learn';
- $self->{control} = 1;
-
- return $self->do_cmd ($msg);
-}
-
-=head2 weights
-
-public instance (\%) weights (String $msg)
-
-Description:
-This method makes a call to the spamd showing weights of message by each statfile.
-
-=cut
-sub weights {
- my ($self, $msg) = @_;
-
- $self->{command} = 'weights';
- $self->{control} = 1;
-
- return $self->do_cmd ($msg);
-}
-
-=head2 fuzzy_add
-
-public instance (\%) fuzzy_add (String $msg)
-
-Description:
-This method makes a call to the spamd adding specified message to fuzzy storage.
-
-=cut
-sub fuzzy_add {
- my ($self, $msg) = @_;
-
- $self->{command} = 'fuzzy_add';
- $self->{control} = 1;
-
- return $self->do_cmd ($msg);
-}
-=head2 fuzzy_del
-
-public instance (\%) fuzzy_add (String $msg)
-
-Description:
-This method makes a call to the spamd removing specified message from fuzzy storage.
-
-=cut
-sub fuzzy_del {
- my ($self, $msg) = @_;
-
- $self->{command} = 'fuzzy_del';
- $self->{control} = 1;
-
- return $self->do_cmd ($msg);
-}
-
-=head2 stat
-
-public instance (\%) stat ()
-
-Description:
-This method makes a call to the spamd and get statistics.
-
-=cut
-sub stat {
- my ($self) = @_;
-
- $self->{command} = 'stat';
- $self->{control} = 1;
-
- return $self->do_cmd (undef);
-}
-=head2 uptime
-
-public instance (\%) uptime ()
-
-Description:
-This method makes a call to the spamd and get uptime.
-
-=cut
-sub uptime {
- my ($self) = @_;
-
- $self->{command} = 'uptime';
- $self->{control} = 1;
-
- return $self->do_cmd (undef);
-}
-=head2 counters
-
-public instance (\%) counters ()
-
-Description:
-This method makes a call to the spamd and get counters.
-
-=cut
-sub counters {
- my ($self) = @_;
-
- $self->{command} = 'counters';
- $self->{control} = 1;
-
- return $self->do_cmd (undef);
-}
-
-=head2 ping
-
-public instance (Boolean) ping ()
-
-Description:
-This method performs a server ping and returns 0 or 1 depending on
-if the server responded correctly.
-
-=cut
-
-sub ping {
- my $self = shift;
- my $host = shift;
-
- my $remote;
- $self->{control} = 0;
- if (defined($host)) {
- $remote = $self->_create_connection($host);
- }
- else {
- # Create connection to random host from cluster
- $remote = $self->_create_connection();
- }
-
- return undef unless $remote;
- local $SIG{PIPE} = 'IGNORE';
-
- if (!(syswrite($remote, "PING $PROTOVERSION$EOL"))) {
- $self->_mark_dead($remote);
- close($remote);
- return 0;
- }
- syswrite($remote, $EOL);
-
- return undef unless $self->_get_io_readiness($remote, 0);
- my $line;
- sysread ($remote, $line, 255);
- close $remote;
- return undef unless $line;
-
- my ($version, $resp_code, $resp_msg) = $self->_parse_response_line($line);
- return 0 unless (defined($resp_msg) && $resp_msg eq 'PONG');
-
- return 1;
-}
-
-=head1 PRIVATE METHODS
-
-=head2 _connect_host
-private instance (IO::Socket) _create_host ($def)
-
-Description:
-This method sets up a proper IO::Socket connection based on the arguments
-used when greating the client object.
-
-On failure, it sets an internal error code and returns undef.
-=cut
-
-sub _connect_host {
- my ($self, $hostdef) = @_;
-
- my $remote;
-
- if ($hostdef =~ /^\//) {
- if (! socket ($remote, PF_UNIX, SOCK_STREAM, 0)) {
- carp "Cannot create unix socket\n";
- return undef;
- }
- my $sun = sockaddr_un($hostdef);
- if (!connect ($remote, $sun)) {
- carp "Cannot connect to socket $hostdef\n";
- close $remote;
- return undef;
- }
- }
- elsif ($hostdef =~ /^\s*(([^:]+):(\d+))\s*$/) {
- my $peer_addr = $2;
- if ($2 eq '*') {
- $peer_addr = '127.0.0.1';
- }
- $remote = IO::Socket::INET->new( Proto => "tcp",
- PeerAddr => $peer_addr,
- PeerPort => $3,
- Blocking => 0,
- );
- # Get write readiness
- if (defined ($remote)) {
- if ($self->_get_io_readiness($remote, 1) != 0) {
- return $remote;
- }
- else {
- close ($remote);
- return undef;
- }
- }
- }
- elsif ($hostdef =~ /^\s*([^:]+)\s*$/) {
- my $peer_addr = $1;
- if ($1 eq '*') {
- $peer_addr = '127.0.0.1';
- }
- $remote = IO::Socket::INET->new( Proto => "tcp",
- PeerAddr => $peer_addr,
- PeerPort => $self->{control} ? 11334 : 11333,
- Blocking => 0,
- );
- # Get write readiness
- if (defined ($remote)) {
- if ($self->_get_io_readiness($remote, 1) != 0) {
- return $remote;
- }
- else {
- close ($remote);
- return undef;
- }
- }
- }
-
- unless ($remote) {
- $self->{error} = "Failed to create connection to spamd daemon: $!\n";
- return undef;
- }
- $remote;
-
-}
-
-=head2 _create_connection
-private instance (IO::Socket) _create_connection ()
-
-Description:
-This method sets up a proper IO::Socket connection based on the arguments
-used when greating the client object.
-
-On failure, it sets an internal error code and returns undef.
-
-=cut
-
-sub _create_connection {
- my ($self, $hostdef) = @_;
-
- my $tries = 0;
-
- if (!defined ($hostdef)) {
- my $server;
-
- do {
- $server = $self->_select_server();
- $tries ++;
-
- my $remote = $self->_connect_host ($server);
-
- return $remote if $remote;
- } while ($tries < 5);
-
- return undef;
- }
-
- return $self->_connect_host ($hostdef);
-}
-
-=head2 _auth
-
-private instance (IO::Socket) _auth (Socket sock)
-
-Description:
-This method do control auth.
-
-On failure this method returns 0
-
-=cut
-sub _auth {
- my ($self, $sock) = @_;
-
- local $SIG{PIPE} = 'IGNORE';
-
- if (!(syswrite($sock, "password $self->{password}$EOL"))) {
- $self->_mark_dead($remote);
- return 0;
- }
-
- return 0 unless $self->_get_io_readiness($sock, 0);
-
- if (sysread($sock, $reply, 255)) {
- if ($reply =~ /^password accepted/) {
- return 0 unless $self->_get_io_readiness($sock, 0);
- # read "END"
- sysread($sock, $reply, 255);
- return 1;
- }
- }
-
- return 0;
-
-}
-
-=head2 _revive_dead
-
-private instance (IO::Socket) _revive_dead ()
-
-Description:
-This method marks dead upstreams as alive
-
-=cut
-sub _revive_dead {
- my ($self) = @_;
-
- my $now = time();
- foreach my $s ($self->{dead_hosts}) {
- # revive after minute of downtime
- if (defined($s->{dead}) && $s->{dead} == 1 && $now - $s->{t} > 60) {
- $s->{dead} = 0;
- push(@{$self->{alive_hosts}}, $s->{host});
- }
- }
-
- 1;
-}
-
-=head2 _select_server
-
-private instance (IO::Socket) _select_server ()
-
-Description:
-This method returns one server from rspamd cluster or undef if there are no suitable ones
-
-=cut
-sub _select_server {
- my($self) = @_;
-
- return undef unless $self->{alive_hosts};
-
- $self->_revive_dead();
- my $alive_num = scalar(@{$self->{alive_hosts}});
- if (!$alive_num) {
- $self->{alive_hosts} = $self->{hosts};
- $self->{dead_hosts} = ();
- $alive_num = scalar($self->{alive_hosts});
- }
-
- my $selected = $self->{alive_hosts}[int(rand($alive_num))];
-
- $selected;
-}
-
-
-=head2 _select_server
-
-private instance (IO::Socket) _mark_dead (String server)
-
-Description:
-This method marks upstream as dead for some time. It can be revived by _revive_dead method
-
-=cut
-sub _mark_dead {
- my ($self, $server) = @_;
-
- return undef unless $self->{hosts};
- my $now = time();
- $self->{dead_hosts}->{$server} = {
- host => $server,
- dead => 1,
- t => $now,
- };
- for (my $i = 0; $i < scalar (@{$self->{alive_hosts}}); $i ++) {
- if ($self->{alive_hosts} == $server) {
- splice(@{$self->{alive_hosts}}, $i, 1);
- last;
- }
- }
-}
-
-=head2 _get_io_readiness
-
-private instance (IO::Socket) _mark_dead (String server)
-
-Description:
-This method marks upstream as dead for some time. It can be revived by _revive_dead method
-
-=cut
-sub _get_io_readiness {
- my ($self, $sock, $is_write) = @_;
- my $w = '';
- vec($w, fileno($sock), 1) = 1;
-
- if ($is_write) {
- return select(undef, $w, undef, $self->{timeout});
- }
- else {
- return select($w, undef,undef, $self->{timeout});
- }
-
- undef;
-}
-
-=head2 _parse_response_line
-
-private instance (@) _parse_response_line (String $line)
-
-Description:
-This method parses the initial response line/header from the server
-and returns its parts.
-
-We have this as a seperate method in case we ever decide to get fancy
-with the response line.
-
-=cut
-
-sub _parse_response_line {
- my ($self, $line) = @_;
-
- $line =~ s/\r?\n$//;
- return split(/\s+/, $line, 3);
-}
-
-sub _write_message {
- my $self = shift;
- my $remote = shift;
- my $message = shift;
- my $len = shift;
-
- my $written = 0;
-
- while ($written < $len) {
- last unless ($self->_get_io_readiness($remote, 1));
- my $cur = syswrite $remote, $message, $len, $written;
-
- last if ($cur <= 0);
- $written += $cur;
- }
-
- return $written == $len;
-}
-
-=head2 _clear_errors
-
-private instance () _clear_errors ()
-
-Description:
-This method clears out any current errors.
-
-=cut
-
-sub _clear_errors {
- my ($self) = @_;
-
- $self->{resp_code} = undef;
- $self->{resp_msg} = undef;
- $self->{error} = undef;
-}
-
-# Currently just read stdin for user's message and pass it to rspamd
-sub _do_rspamc_command {
- my ($self, $remote, $msg) = @_;
-
- my %metrics;
- my ($in, $res);
-
- my $msgsize = length($msg);
-
- local $SIG{PIPE} = 'IGNORE';
-
- if (!(syswrite($remote, "$self->{command} $PROTOVERSION$EOL"))) {
- $self->_mark_dead($remote);
- my %r = (
- error => 'cannot connect to rspamd',
- error_code => 502,
- );
- close($remote);
- return \%r;
- }
- syswrite $remote, "Content-length: $msgsize$EOL";
- syswrite $remote, "User: $self->{username}$EOL" if (exists($self->{username}));
- syswrite $remote, "From: $self->{from}$EOL" if (exists($self->{from}));
- syswrite $remote, "IP: $self->{ip}$EOL" if (exists($self->{ip}));
- syswrite $remote, "Deliver-To: $self->{deliver_to}$EOL" if (exists($self->{deliver_to}));
- syswrite $remote, "Subject: $self->{subject}$EOL" if (exists($self->{subject}));
- syswrite $remote, "Pass: all$EOL" if (exists($self->{pass_all}) && $self->{pass_all});
- if (ref $self->{rcpt} eq "ARRAY") {
- foreach ($self->{rcpt}) {
- syswrite $remote, "Rcpt: $_ $EOL";
- }
- }
- syswrite $remote, $EOL;
-
- if (! $self->_write_message($remote, $msg, $msgsize)) {
- my %r = (
- error => 'error writing message to rspamd',
- error_code => 502,
- );
- close $remote;
- return \%r;
- }
-
- #syswrite $remote, $EOL;
-
- unless ($self->_get_io_readiness($remote, 0)) {
- close $remote;
- my %r = (
- error => 'timed out while waiting for reply',
- error_code => 502,
- );
- return \%r;
- }
-
- my $offset = 0;
- do {
- $res = sysread($remote, $in, 512, $offset);
- if (!defined ($res)) {
- close $remote;
- my %r = (
- error => 'IO error while reading data from socket: ' . $!,
- error_code => 503,
- );
- return \%r;
- }
- if ($res > 0 && $res < 512) {
- $self->_get_io_readiness($remote, 0);
- }
- $offset += $res;
- } while ($res > 0);
-
- my ($version, $resp_code, $resp_msg) = $self->_parse_response_line($in);
-
- $self->{resp_code} = $resp_code;
- $self->{resp_msg} = $resp_msg;
-
- unless (defined($resp_code) && $resp_code == 0) {
- close $remote;
- my %r = (
- error => 'invalid reply',
- error_code => 500,
- );
- return \%r
- }
-
- my $cur_metric;
- my @lines = split (/^/, $in);
- if (lc $self->{'command'} eq 'urls') {
- $metrics{'default'} = {
- isspam => 'false',
- score => 0,
- threshold => 0,
- symbols => [],
- urls => [],
- messages => [],
- action => 'reject',
- };
- foreach my $line (@lines) {
- if ($line =~ /^Urls: (.+)$/) {
- @{ $metrics{'default'}->{'urls'} } = split /,\s+/, $1;
- }
- }
- }
- else {
- foreach my $line (@lines) {
- if ($line =~ m!Metric: (\S+); (\S+); (\S+) / (\S+) (/ (\S+))?!) {
- $metrics{$1} = {
- isspam => $2,
- score => $3 + 0,
- threshold => $4 + 0,
- reject_score => $6,
- symbols => [],
- urls => [],
- messages => [],
- action => 'no action',
- };
- $cur_metric = $1;
- }
- elsif ($line =~ /^Symbol: (\S+);\s*(.+?)\s*$/ && $cur_metric) {
- # Line with parameters
- my $symref = $metrics{$cur_metric}->{'symbols'};
- push(@$symref, "$1($2)");
- }
- elsif ($line =~ /^Symbol: (\S+?)\s*$/ && $cur_metric) {
- my $symref = $metrics{$cur_metric}->{'symbols'};
- push(@$symref, $1);
- }
- elsif ($line =~ /^Urls: (.+?)\s*$/ && $cur_metric) {
- @{ $metrics{$cur_metric}->{'urls'} } = split /,\s+/, $1;
- }
- elsif ($line =~ /^Message: (.+?)\s*$/ && $cur_metric) {
- my $symref = $metrics{$cur_metric}->{'messages'};
- push(@$symref, $1);
- }
- elsif ($line =~ /^Action: (.+?)\s*$/ && $cur_metric) {
- $metrics{$cur_metric}->{'action'} = $1;
- }
- elsif ($line =~ /^${EOL}$/) {
- last;
- }
- }
- }
-
- close $remote;
-
- return \%metrics;
-
-}
-
-
-sub _do_control_command {
- my ($self, $remote, $msg) = @_;
-
- local $SIG{PIPE} = 'IGNORE';
- my %res = (
- error_code => 0,
- error => '',
- );
-
- unless ($self->_get_io_readiness($remote, 0)) {
- $res{error} = "Timeout while reading data from socket";
- $res{error_code} = 501;
- close($remote);
- return \%res;
- }
-
- # Read greeting first
- if (defined (my $greeting = <$remote>)) {
- if ($greeting !~ /^Rspamd version/) {
- $res{error} = "Not rspamd greeting line $greeting";
- $res{error_code} = 500;
- close($remote);
- return \%res;
- }
- }
-
- if ($self->{'command'} =~ /^learn$/i) {
- if (!$self->{'statfile'}) {
- $res{error} = "Statfile is not specified to learn command";
- $res{error_code} = 500;
- close($remote);
- return \%res;
- }
-
- if ($self->_auth ($remote)) {
- my $len = length ($msg);
- syswrite $remote, "learn $self->{statfile} $len -m $self->{weight}" . $EOL;
- if (! $self->_write_message($remote, $msg, length($msg))) {
- $res{error} = 'error writing message to rspamd';
- $res{error_code} = 502;
- close $remote;
- return \%res;
- }
- unless ($self->_get_io_readiness($remote, 0)) {
- $res{error} = "Timeout while reading data from socket";
- $res{error_code} = 501;
- close($remote);
- return \%res;
- }
- if (defined (my $reply = <$remote>)) {
- if ($reply =~ /^learn ok, sum weight: ([0-9.]+)/) {
- $res{error} = "Learn succeed. Sum weight: $1\n";
- close($remote);
- return \%res;
- }
- else {
- $res{error_code} = 500;
- $res{error} = "Learn failed: $reply\n";
- close($remote);
- return \%res;
- }
- }
- }
- else {
- $res{error_code} = 403;
- $res{error} = "Authentication failed\n";
- close($remote);
- return \%res;
- }
- }
- elsif ($self->{'command'} =~ /^weights$/i) {
- if (!$self->{'statfile'}) {
- $res{error_code} = 500;
- $res{error} = "Statfile is not specified to weights command";
- close($remote);
- return \%res;
- }
-
- my $len = length ($msg);
- $res{error} = "Sending $len bytes...\n";
- syswrite $remote, "weights $self->{'statfile'} $len" . $EOL;
- if (! $self->_write_message($remote, $msg, length($msg))) {
- $res{error} = 'error writing message to rspamd';
- $res{error_code} = 502;
- close $remote;
- return \%res;
- }
- unless ($self->_get_io_readiness($remote, 0)) {
- $res{error} = "Timeout while reading data from socket";
- $res{error_code} = 501;
- close($remote);
- return \%res;
- }
- while (defined (my $reply = <$remote>)) {
- last if $reply =~ /^END/;
- $res{error} .= $reply;
- }
- }
- elsif ($self->{'command'} =~ /(reload|shutdown)/i) {
- if ($self->_auth ($remote)) {
- syswrite $remote, $self->{'command'} . $EOL;
- unless ($self->_get_io_readiness($remote, 0)) {
- $res{error} = "Timeout while reading data from socket";
- $res{error_code} = 501;
- close($remote);
- return \%res;
- }
- while (defined (my $line = <$remote>)) {
- last if $line =~ /^END/;
- $res{error} .= $line;
- }
- }
- else {
- $res{error_code} = 403;
- $res{error} = "Authentication failed\n";
- close($remote);
- return \%res;
- }
- }
- elsif ($self->{'command'} =~ /(fuzzy_add|fuzzy_del)/i) {
- if ($self->_auth ($remote)) {
- my $len = length ($msg);
- syswrite $remote, $self->{'command'} . " $len $self->{'weight'}" . $EOL;
- if (! $self->_write_message($remote, $msg, length($msg))) {
- $res{error} = 'error writing message to rspamd';
- $res{error_code} = 502;
- close $remote;
- return \%res;
- }
- unless ($self->_get_io_readiness($remote, 0)) {
- $res{error} = "Timeout while reading data from socket";
- $res{error_code} = 501;
- close($remote);
- return \%res;
- }
- if (defined (my $reply = <$remote>)) {
- if ($reply =~ /^OK/) {
- $res{error} = $self->{'command'} . " succeed\n";
- close($remote);
- return \%res;
- }
- else {
- $res{error_code} = 500;
- $res{error} = $self->{'command'} . " failed\n";
- close($remote);
- return \%res;
- }
- }
- }
- else {
- $res{error_code} = 403;
- $res{error} = "Authentication failed\n";
- close($remote);
- return \%res;
- }
-
- }
- else {
- syswrite $remote, $self->{'command'} . $EOL;
- unless ($self->_get_io_readiness($remote, 0)) {
- $res{error} = "Timeout while reading data from socket";
- $res{error_code} = 501;
- close($remote);
- return \%res;
- }
- while (defined (my $line = <$remote>)) {
- last if $line =~ /^END/;
- $res{error} .= $line;
- }
- }
-
- close($remote);
- return \%res;
-}
-
-sub _process_file {
- my $self = shift;
- my $file = shift;
- my $cb = shift;
- my $res;
-
- open(FILE, "< $file") or return;
-
- my $input;
- while (defined (my $line = <FILE>)) {
- $input .= $line;
- }
-
- close FILE;
- $res = $self->do_all_cmd ($input);
- if (defined ($cb) && $res) {
- $cb->($file, $res);
- }
-}
-
-sub _process_directory {
- my $self = shift;
- my $dir = shift;
- my $cb = shift;
-
- opendir (DIR, $dir) or return;
-
- while (defined (my $file = readdir (DIR))) {
- $file = "$dir/$file";
- if (-f $file) {
- $self->_process_file ($file, $cb);
- }
- }
- closedir (DIR);
-}
-
-sub _check_imap_reply {
- my $self = shift;
- my $sock = shift;
- my $seq = shift;
-
- my $input;
-
- while (defined ($input = <$sock>)) {
- chomp $input;
- if ($input =~ /BAD|NO (.+)$/) {
- $_[0] = $1;
- return 0;
- }
- next if ($input =~ /^\*/);
- if ($input =~ /^$seq OK/) {
- return 1;
- }
-
- $_[0] = $input;
- return 0;
- }
-
- $_[0] = "timeout";
-
- return 0;
-}
-
-sub _parse_imap_body {
- my $self = shift;
- my $sock = shift;
- my $seq = shift;
- my $input;
- my $got_body = 0;
-
- while (defined (my $line = <$sock>)) {
- if (!$got_body && $line =~ /^\*/) {
- $got_body = 1;
- next;
- }
- if ($line =~ /^$seq OK/) {
- return $input;
- }
- elsif ($got_body) {
- $input .= $line;
- next;
- }
-
- return undef;
- }
-
- return undef;
-
-}
-
-sub _parse_imap_sequences {
- my $self = shift;
- my $sock = shift;
- my $seq = shift;
- my $input;
-
- while (defined ($input = <$sock>)) {
- chomp $input;
- if ($input =~ /^\* SEARCH (.+)$/) {
- @res = split (/\s/, $1);
- next;
- }
- elsif ($input =~ /^$seq OK/) {
- return \@res;
- }
- return undef;
- }
-
-}
-
-sub _process_imap {
- my ($self, $ssl, $user, $password, $host, $mbox, $cb) = @_;
- my $seq = 1;
- my $sock;
- my $res;
-
- if (!$password) {
- eval {
- require Term::ReadKey;
- Term::ReadKey->import( qw(ReadMode ReadLine) );
- print "Enter IMAP password: ";
- ReadMode(2);
- $password = ReadLine(0);
- chomp $password;
- ReadMode(0);
- print "\n";
- } or croak "cannot get password. Check that Term::ReadKey is installed";
- }
-
- # Stupid code that does not take care of timeouts etc, just trying to extract messages
- if ($ssl) {
- $sock = $self->_make_ssl_socket ($host, 'imaps');
- }
- else {
- $sock = IO::Socket::INET->new( Proto => "tcp",
- PeerAddr => $host,
- PeerPort => 'imap',
- Blocking => 1,
- );
- }
- unless ($sock) {
- $self->{error} = "Cannot connect to imap server: $!";
- return;
- }
- my $reply = <$sock>;
- if (!defined ($reply) || $reply !~ /^\* OK/) {
- $self->{error} = "Imap server is not ready";
- return;
- }
- syswrite $sock, "$seq LOGIN $user $password$EOL";
- if (!$self->_check_imap_reply ($sock, $seq, $reply)) {
- $self->{error} = "Cannot login to imap server: $reply";
- return;
- }
- $seq ++;
- syswrite $sock, "$seq SELECT $mbox$EOL";
- if (!$self->_check_imap_reply ($sock, $seq, $reply)) {
- $self->{error} = "Cannot select mbox $mbox: $reply";
- return;
- }
- $seq ++;
- syswrite $sock, "$seq SEARCH $self->{imap_search}$EOL";
- my $messages;
- if (!defined ($messages = $self->_parse_imap_sequences ($sock, $seq))) {
- $self->{error} = "Cannot make search";
- return;
- }
- $seq ++;
- foreach my $message (@{ $messages }){
- syswrite $sock, "$seq FETCH $message body[]$EOL";
- if (defined (my $input = $self->_parse_imap_body ($sock, $seq))) {
- $self->do_all_cmd ($input);
- if (defined ($cb) && $res) {
- $cb->($seq, $res);
- }
- }
- $seq ++;
- }
- syswrite $sock, "$seq LOGOUT$EOL";
- close $sock;
-}
-
-1;
diff --git a/perl/lib/Mail/Rspamd/Config.pm b/perl/lib/Mail/Rspamd/Config.pm
deleted file mode 100644
index 7f3d8fefd..000000000
--- a/perl/lib/Mail/Rspamd/Config.pm
+++ /dev/null
@@ -1,593 +0,0 @@
-=head1 NAME
-
-Mail::Rspamd::Config - Utilities for rspamd configuration
-
-=head1 SYNOPSIS
-
-=head1 DESCRIPTION
-
-Mail::Rspamd::Config is a module that provides a perl implementation for
-configuring rspamd.
-
-=cut
-
-package Mail::Rspamd::Config;
-
-use Carp;
-use XML::Parser;
-
-use vars qw($VERSION);
-$VERSION = "1.02";
-
-use constant PARSER_STATE_START => 0;
-use constant PARSER_STATE_MAIN => 1;
-use constant PARSER_STATE_WORKER => 2;
-use constant PARSER_STATE_MODULE => 3;
-use constant PARSER_STATE_CLASSIFIER => 4;
-use constant PARSER_STATE_STATFILE => 5;
-use constant PARSER_STATE_LOGGING => 6;
-use constant PARSER_STATE_METRIC => 8;
-use constant PARSER_STATE_VIEW => 9;
-use constant PARSER_STATE_MODULES => 10;
-use constant PARSER_STATE_END => -1;
-
-
-=head1 PUBLIC METHODS
-
-=head2 new
-
-public class (Mail::Rspamd::Config) new (\% $args)
-
-Description:
-This method creates a new Mail::Rspamd::Config object.
-
-=cut
-
-sub new {
- my ($class, $args) = @_;
-
- $class = ref($class) || $class;
-
- my $self = {
- workers => [],
- modules => {},
- classifiers => {},
- metrics => {},
- options => {},
- variables => {},
- logging => {},
- lua => [],
- composites => {},
- paths => [],
- views => [],
- parser_state => {
- state => PARSER_STATE_START,
- valid => 1,
- },
- };
-
- if (defined ($args->{'file'})) {
- $self->{'file'} = $args->{'file'}
- }
-
-
- bless($self, $class);
-
- $self;
-}
-
-=head2 load
-
-public load (String $file)
-
-Description:
-Loads rspamd config file and parses it.
-
-=cut
-
-sub load {
- my ($self, $file) = @_;
-
- if (defined ($file)) {
- $self->{'file'} = $file;
- }
-
- if (!defined ($self->{'file'}) || ! -f $self->{'file'}) {
- carp 'cannot open file specified';
- return undef;
- }
-
- my $parser = new XML::Parser(Handlers => {Start => sub { $self->_handle_start_element(@_) },
- End => sub { $self->_handle_end_element(@_) },
- Char => sub { $self->_handle_text(@_) } });
-
- $parser->parsefile($self->{file});
-}
-
-=head2 save
-
-public save (String $file)
-
-Description:
-Dumps rspamd config to xml file.
-
-=cut
-
-sub save {
- my ($self, $file) = @_;
-
- if (defined ($file)) {
- $self->{'file'} = $file;
- }
-
- if (!defined ($self->{'file'})) {
- carp 'cannot open file specified';
- return undef;
- }
-
- $self->_dump();
-}
-
-=head2 _handle_start_element
-
-private _handle_start_element($parser, $element, [attr, value...])
-
-Description:
-Handle start xml tag of rspamd
-
-=cut
-sub _handle_start_element {
- my ($self, $parser, $element, @attrs) = @_;
-
-
- if ($self->{parser_state}->{valid}) {
- # Start element
- $self->{parser_state}->{element} = lc $element;
-
- if ($self->{parser_state}->{state} == PARSER_STATE_START) {
- if (lc $element eq 'rspamd') {
- $self->{parser_state}->{state} = PARSER_STATE_MAIN;
- }
- else {
- $self->{parser_state}->{valid} = 0;
- $self->{error} = 'Start element missing, it must be <rspamd>, but is <' . $element . '>';
- }
- }
- # Main section
- elsif ($self->{parser_state}->{state} == PARSER_STATE_MAIN) {
- my $lce = lc $element;
- if ($lce eq 'logging') {
- $self->{parser_state}->{state} = PARSER_STATE_LOGGING;
- }
- elsif ($lce eq 'worker') {
- $self->{parser_state}->{state} = PARSER_STATE_WORKER;
- $self->{parser_state}->{worker} = { options => {} };
- }
- elsif ($lce eq 'view') {
- $self->{parser_state}->{state} = PARSER_STATE_VIEW;
- $self->{parser_state}->{view} = {};
- }
- elsif ($lce eq 'metric') {
- $self->{parser_state}->{state} = PARSER_STATE_METRIC;
- $self->{parser_state}->{metric} = { symbols => {} };
- }
- elsif ($lce eq 'module') {
- $self->{parser_state}->{state} = PARSER_STATE_MODULE;
- $self->_get_attr('name', 'name', 1, @attrs);
- $self->{parser_state}->{module} = {};
- }
- elsif ($lce eq 'classifier') {
- $self->{parser_state}->{state} = PARSER_STATE_CLASSIFIER;
- $self->_get_attr('type', 'type', 1, @attrs);
- $self->{parser_state}->{classifier} = { statfiles => []};
- }
- elsif ($lce eq 'variable') {
- $self->_get_attr('name', 'name', 1, @attrs);
- }
- elsif ($lce eq 'lua') {
- $self->_get_attr('src', 'src', 1, @attrs);
- }
- elsif ($lce eq 'composite') {
- $self->_get_attr('name', 'name', 1, @attrs);
- }
- elsif ($lce eq 'modules') {
- $self->{parser_state}->{state} = PARSER_STATE_MODULES;
- }
- else {
- # Other element
- $self->{parser_state}->{element} = $lce;
- }
- }
- elsif ($self->{parser_state}->{state} == PARSER_STATE_MODULE) {
- my $lce = lc $element;
- if ($lce eq 'option') {
- $self->_get_attr('name', 'option', 1, @attrs);
- }
- else {
- $self->{parser_state}->{valid} = 0;
- $self->{error} = 'Invalid tag <' . $lce . '> in module section';
- }
- }
- elsif ($self->{parser_state}->{state} == PARSER_STATE_METRIC) {
- my $lce = lc $element;
- if ($lce eq 'symbol') {
- $self->_get_attr('weight', 'weight', 1, @attrs);
- }
- }
- elsif ($self->{parser_state}->{state} == PARSER_STATE_CLASSIFIER) {
- my $lce = lc $element;
- if ($lce eq 'statfile') {
- $self->{parser_state}->{state} = PARSER_STATE_STATFILE;
- }
- }
- elsif ($self->{parser_state}->{state} == PARSER_STATE_WORKER) {
- my $lce = lc $element;
- if ($lce eq 'param') {
- $self->_get_attr('name', 'name', 1, @attrs);
- }
- }
- elsif ($self->{parser_state}->{state} == PARSER_STATE_END) {
- # Tags after end element
- $self->{parser_state}->{valid} = 0;
- $self->{error} = 'Invalid tag <' . $element . '> after end tag';
- }
- else {
- # On other states just set element
- }
- }
-}
-
-
-=head2 _handle_end_element
-
-private _handle_end_element($parser, $element)
-
-Description:
-Handle end xml tag of rspamd
-
-=cut
-sub _handle_end_element {
- my ($self, $parser, $element) = @_;
-
- if ($self->{parser_state}->{valid}) {
- my $lce = lc $element;
- if ($self->{parser_state}->{state} == PARSER_STATE_MAIN) {
- if ($lce eq 'rspamd') {
- $self->{parser_state}->{state} = PARSER_STATE_END;
- }
- }
- elsif ($self->{parser_state}->{state} == PARSER_STATE_WORKER) {
- if ($lce eq 'worker') {
- push(@{$self->{workers}}, $self->{parser_state}->{worker});
- $self->{parser_state}->{state} = PARSER_STATE_MAIN;
- $self->{parser_state}->{worker} = undef;
- }
- }
- elsif ($self->{parser_state}->{state} == PARSER_STATE_CLASSIFIER) {
- if ($lce eq 'classifier') {
- $self->{classifiers}->{ $self->{parser_state}->{type} } = $self->{parser_state}->{classifier};
- $self->{parser_state}->{state} = PARSER_STATE_MAIN;
- $self->{parser_state}->{classifier} = undef;
- }
- }
- elsif ($self->{parser_state}->{state} == PARSER_STATE_METRIC) {
- if ($lce eq 'metric') {
- if (exists ($self->{parser_state}->{metric}->{name})) {
- $self->{metrics}->{ $self->{parser_state}->{metric}->{name} } = $self->{parser_state}->{metric};
- $self->{parser_state}->{state} = PARSER_STATE_MAIN;
- $self->{parser_state}->{metric} = undef;
- }
- else {
- $self->{parser_state}->{valid} = 0;
- $self->{error} = 'Metric must have <name> tag';
- }
- }
- }
- elsif ($self->{parser_state}->{state} == PARSER_STATE_STATFILE) {
- if ($lce eq 'statfile') {
- push(@{$self->{parser_state}->{classifier}->{statfiles}}, $self->{parser_state}->{statfile});
- $self->{parser_state}->{state} = PARSER_STATE_CLASSIFIER;
- $self->{parser_state}->{statfile} = undef;
- }
- }
- elsif ($self->{parser_state}->{state} == PARSER_STATE_MODULE) {
- if ($lce eq 'module') {
- $self->{modules}->{ $self->{parser_state}->{name} } = $self->{parser_state}->{module};
- $self->{parser_state}->{state} = PARSER_STATE_MAIN;
- $self->{parser_state}->{module} = undef;
- }
- }
- elsif ($self->{parser_state}->{state} == PARSER_STATE_LOGGING) {
- if ($lce eq 'logging') {
- $self->{parser_state}->{state} = PARSER_STATE_MAIN;
- }
- }
- elsif ($self->{parser_state}->{state} == PARSER_STATE_VIEW) {
- if ($lce eq 'view') {
- push(@{$self->{views}}, $self->{parser_state}->{view});
- $self->{parser_state}->{state} = PARSER_STATE_MAIN;
- $self->{parser_state}->{view} = undef;
- }
- }
- elsif ($self->{parser_state}->{state} == PARSER_STATE_MODULES) {
- if ($lce eq 'modules') {
- $self->{parser_state}->{state} = PARSER_STATE_MAIN;
- }
- }
- }
-}
-
-=head2 _handle_text
-
-private _handle_text($parser, $string)
-
-Description:
-Handle data of xml tag
-
-=cut
-sub _handle_text {
- my ($self, $parser, $string) = @_;
-
- my $data;
-
- if (defined ($string) && $string =~ /^\s*(\S*(?:\s+\S+)*)\s*$/) {
- $data = $1;
- }
- else {
- return undef;
- }
- if (!$data) {
- return undef;
- }
-
- if ($self->{parser_state}->{valid}) {
- if ($self->{parser_state}->{state} == PARSER_STATE_MAIN) {
- if ($self->{parser_state}->{element} eq 'variable') {
- $self->{variables}->{ $self->{parser_state}->{name} } = $data;
- }
- elsif ($self->{parser_state}->{element} eq 'composite') {
- $self->{composites}->{ $self->{parser_state}->{name} } = $data;
- }
- elsif ($self->{parser_state}->{element} eq 'lua') {
- push(@{$self->{lua}}, $self->{parser_state}->{src});
- }
- else {
- $self->{options}->{ $self->{parser_state}->{element} } = $data;
- }
- }
- elsif ($self->{parser_state}->{state} == PARSER_STATE_LOGGING) {
- $self->{logging}->{ $self->{parser_state}->{element} } = $data;
- }
- elsif ($self->{parser_state}->{state} == PARSER_STATE_WORKER) {
- if ($self->{parser_state}->{element} eq 'param' || $self->{parser_state}->{element} eq 'option') {
- $self->{parser_state}->{worker}->{options}->{$self->{parser_state}->{name}} = $data;
- }
- else {
- $self->{parser_state}->{worker}->{ $self->{parser_state}->{element} } = $data;
- }
- }
- elsif ($self->{parser_state}->{state} == PARSER_STATE_CLASSIFIER) {
- $self->{parser_state}->{classifier}->{ $self->{parser_state}->{element} } = $data;
- }
- elsif ($self->{parser_state}->{state} == PARSER_STATE_STATFILE) {
- $self->{parser_state}->{statfile}->{ $self->{parser_state}->{element} } = $data;
- }
- elsif ($self->{parser_state}->{state} == PARSER_STATE_MODULE) {
- $self->{parser_state}->{module}->{ $self->{parser_state}->{option} } = $data;
- }
- elsif ($self->{parser_state}->{state} == PARSER_STATE_VIEW) {
- $self->{parser_state}->{view}->{ $self->{parser_state}->{option} } = $data;
- }
- elsif ($self->{parser_state}->{state} == PARSER_STATE_MODULES) {
- push(@{$self->{paths}}, $data);
- }
- elsif ($self->{parser_state}->{state} == PARSER_STATE_METRIC) {
- if ($self->{parser_state}->{element} eq 'symbol') {
- $self->{parser_state}->{metric}->{symbols}->{ $data } = $self->{parser_state}->{weight};
- }
- else {
- $self->{parser_state}->{metric}->{ $self->{parser_state}->{element} } = $data;
- }
- }
- }
-}
-
-=head2 _get_attr
-
-private _get_attr($name, $hash_name, $required, @attrs)
-
-Description:
-Extract specified attr and put it to parser_state
-
-=cut
-sub _get_attr {
- my ($self, $name, $hash_name, $required, @attrs) = @_;
- my $found = 0;
- my $param = 1;
-
- foreach (@attrs) {
- if ($found) {
- $self->{parser_state}->{$hash_name} = $_;
- last;
- }
- else {
- if ($param) {
- if (lc $_ eq $name) {
- $found = 1;
- }
- $param = 0;
- }
- else {
- $param = 1;
- }
- }
- }
-
- if (!$found && $required) {
- $self->{error} = "Attribute '$name' is required for tag '$self->{parser_state}->{element}'";
- $self->{parser_state}->{'valid'} = 0;
- }
-}
-
-=head2 _dump
-
-private _dump()
-
-Description:
-Dumps rspamd config to xml file
-
-=cut
-sub _dump {
- my ($self) = @_;
-
- open(XML, "> $self->{file}") or carp "cannot open file '$self->file'";
-
- print XML "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<rspamd>\n";
-
- print XML "<!-- Main section -->\n";
- while(my ($k, $v) = each (%{$self->{options}})) {
- my $ek = $self->_xml_escape($k);
- print XML "<$ek>" . $self->_xml_escape($v) . "</$ek>\n";
- }
- foreach my $lua(@{$self->{lua}}) {
- print XML "<lua src=\"". $self->_xml_escape($lua) ."\">lua</lua>\n";
- }
- print XML "<!-- End of main section -->\n\n";
-
- print XML "<!-- Variables section -->\n";
- while(my ($k, $v) = each (%{$self->{variables}})) {
- my $ek = $self->_xml_escape($k);
- print XML "<variable name=\"$ek\">" . $self->_xml_escape($v) . "</variable>\n";
- }
- print XML "<!-- End of variables section -->\n\n";
-
- print XML "<!-- Composites section -->\n";
- while(my ($k, $v) = each (%{$self->{composites}})) {
- my $ek = $self->_xml_escape($k);
- print XML "<composite name=\"$ek\">" . $self->_xml_escape($v) . "</composite>\n";
- }
- print XML "<!-- End of composites section -->\n\n";
-
- print XML "<!-- Workers section -->\n";
- foreach my $worker (@{$self->{workers}}) {
- print XML "<worker>\n";
- while (my ($k, $v) = each (%{$worker})) {
- my $ek = $self->_xml_escape($k);
- if ($k eq 'options') {
- while (my ($kk, $vv) = each (%{$v})) {
- print XML " <param name=\"". $self->_xml_escape($kk) ."\">" . $self->_xml_escape($vv) . "</param>\n";
- }
- }
- else {
- print XML " <$ek>" . $self->_xml_escape($v) . "</$ek>\n";
- }
- }
- print XML "</worker>\n";
- }
- print XML "<!-- End of workers section -->\n\n";
-
- print XML "<!-- Metrics section -->\n";
- while (my ($k, $v) = each (%{$self->{metrics}})) {
- print XML "<metric name=\"". $self->_xml_escape($k) ."\">\n";
- while (my ($kk, $vv) = each (%{ $v })) {
- my $ek = $self->_xml_escape($kk);
- if ($ek eq 'symbols') {
- while (my ($sym, $weight) = each (%{ $vv })) {
- print XML " <symbol weight=\"". $self->_xml_escape($weight) ."\">" . $self->_xml_escape($sym) . "</symbol>\n";
- }
- }
- else {
- print XML " <$ek>" . $self->_xml_escape($vv) . "</$ek>\n";
- }
- }
- print XML "</metric>\n";
- }
- print XML "<!-- End of metrics section -->\n\n";
-
- print XML "<!-- Logging section -->\n<logging>\n";
- while (my ($k, $v) = each (%{$self->{logging}})) {
- my $ek = $self->_xml_escape($k);
- print XML " <$ek>" . $self->_xml_escape($v) . "</$ek>\n";
- }
- print XML "</logging>\n<!-- End of logging section -->\n\n";
-
- print XML "<!-- Classifiers section -->\n";
- while (my ($type, $classifier) = each(%{$self->{classifiers}})) {
- print XML "<classifier type=\"". $self->_xml_escape($type) ."\">\n";
- while (my ($k, $v) = each (%{$classifier})) {
- my $ek = $self->_xml_escape($k);
- if ($k eq 'statfiles') {
- foreach my $statfile (@{$v}) {
- print XML " <statfile>\n";
- while (my ($kk, $vv) = each (%{$statfile})) {
- my $ekk = $self->_xml_escape($kk);
- print XML " <$ekk>" . $self->_xml_escape($vv) . "</$ekk>\n";
- }
- print XML " </statfile>\n";
- }
- }
- else {
- print XML " <$ek>" . $self->_xml_escape($v) . "</$ek>\n";
- }
- }
- print XML "</classifier>\n";
- }
- print XML "<!-- End of classifiers section -->\n\n";
-
- print XML "<!-- Modules section -->\n";
- while (my ($name, $module) = each(%{$self->{modules}})) {
- print XML "<module name=\"". $self->_xml_escape($name) ."\">\n";
- while (my ($k, $v) = each (%{$module})) {
- my $ek = $self->_xml_escape($k);
- print XML " <option name=\"$ek\">" . $self->_xml_escape($v) . "</option>\n";
- }
- print XML "</module>\n";
- }
- print XML "<!-- End of modules section -->\n\n";
-
- print XML "<!-- Paths section -->\n<modules>\n";
- foreach my $module(@{$self->{paths}}) {
- print XML " <module>" . $self->_xml_escape($module) . "</module>\n";
- }
- print XML "</modules>\n<!-- End of paths section -->\n\n";
-
- print XML "</rspamd>\n";
-}
-
-=head2 _xml_escape
-
-private _xml_escape()
-
-Description:
-Escapes characters in xml string
-
-=cut
-sub _xml_escape {
- my $data = $_[1];
- if ($data =~ /[\&\<\>\"]/) {
- $data =~ s/\&/\&amp\;/g;
- $data =~ s/\</\&lt\;/g;
- $data =~ s/\>/\&gt\;/g;
- $data =~ s/\"/\&quot\;/g;
- }
- return $data;
-}
-
-=head2 _xml_unescape
-
-private _xml_unescape()
-
-Description:
-Unescapes characters in xml string
-
-=cut
-sub _xml_unescape {
- my $data = $_[1];
- if ($data =~ /\&amp|\&lt|\&gt|\&quot/) {
- $data =~ s/\&amp;/\&/g;
- $data =~ s/\&lt\;/\</g;
- $data =~ s/\&gt\;/\>/g;
- $data =~ s/\&quot\;/\"/g;
- }
- return $data;
-}
diff --git a/src/buffer.c b/src/buffer.c
index 59dd55d3e..73bb6e654 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -690,9 +690,9 @@ rspamd_dispatcher_write (rspamd_io_dispatcher_t * d,
}
gboolean rspamd_dispatcher_write_string (rspamd_io_dispatcher_t *d,
- GString *str,
- gboolean delayed,
- gboolean free_on_write)
+ GString *str,
+ gboolean delayed,
+ gboolean free_on_write)
{
struct rspamd_out_buffer_s *newbuf;
diff --git a/src/cfg_file.h b/src/cfg_file.h
index d53829675..41c37bd34 100644
--- a/src/cfg_file.h
+++ b/src/cfg_file.h
@@ -36,9 +36,6 @@
#define DEFAULT_SCORE 10.0
#define DEFAULT_REJECT_SCORE 999.0
-#define yyerror parse_err
-#define yywarn parse_warn
-
struct expression;
struct tokenizer;
struct classifier;
@@ -379,6 +376,7 @@ struct config_file {
guint32 dns_retransmits; /**< maximum retransmits count */
guint32 dns_throttling_errors; /**< maximum errors for starting resolver throttling */
guint32 dns_throttling_time; /**< time in seconds for DNS throttling */
+ guint32 dns_io_per_server; /**< number of sockets per DNS server */
GList *nameservers; /**< list of nameservers or NULL to parse resolv.conf */
};
@@ -415,7 +413,7 @@ gboolean parse_host_priority (memory_pool_t *pool, const gchar *str, gchar **add
* @param type type of credits
* @return 1 if line was successfully parsed and 0 in case of error
*/
-gboolean parse_bind_line (struct config_file *cfg, struct worker_conf *cf, gchar *str);
+gboolean parse_bind_line (struct config_file *cfg, struct worker_conf *cf, const gchar *str);
/**
* Init default values
@@ -502,7 +500,9 @@ gboolean parse_normalizer (struct config_file *cfg, struct statfile *st, const g
/*
* Read XML configuration file
*/
-gboolean read_rspamd_config (struct config_file *cfg, const gchar *filename, const gchar *convert_to);
+gboolean read_rspamd_config (struct config_file *cfg,
+ const gchar *filename, const gchar *convert_to,
+ rspamd_rcl_section_fin_t logger_fin, gpointer logger_ud);
/*
* Register symbols of classifiers inside metrics
diff --git a/src/cfg_rcl.c b/src/cfg_rcl.c
index b27dedbdd..e8cb66800 100644
--- a/src/cfg_rcl.c
+++ b/src/cfg_rcl.c
@@ -437,7 +437,6 @@ rspamd_rcl_worker_handler (struct config_file *cfg, ucl_object_t *obj,
const gchar *worker_type, *worker_bind;
GQuark qtype;
struct worker_conf *wrk;
- struct rspamd_worker_bind_conf *bcf;
struct rspamd_worker_cfg_parser *wparser;
struct rspamd_worker_param_parser *whandler;
@@ -475,13 +474,10 @@ rspamd_rcl_worker_handler (struct config_file *cfg, ucl_object_t *obj,
if (!ucl_object_tostring_safe (cur, &worker_bind)) {
continue;
}
- bcf = memory_pool_alloc0 (cfg->cfg_pool, sizeof (struct rspamd_worker_bind_conf));
- if (!parse_host_port_priority (cfg->cfg_pool, worker_bind, &bcf->bind_host,
- &bcf->bind_port, NULL)) {
+ if (!parse_bind_line (cfg, wrk, worker_bind)) {
g_set_error (err, CFG_RCL_ERROR, EINVAL, "cannot parse bind line: %s", worker_bind);
return FALSE;
}
- LL_PREPEND (wrk->bind_conf, bcf);
}
}
@@ -1037,6 +1033,8 @@ rspamd_rcl_config_init (void)
G_STRUCT_OFFSET (struct config_file, dns_timeout), RSPAMD_CL_FLAG_TIME_INTEGER);
rspamd_rcl_add_default_handler (sub, "dns_retransmits", rspamd_rcl_parse_struct_integer,
G_STRUCT_OFFSET (struct config_file, dns_retransmits), RSPAMD_CL_FLAG_INT_32);
+ rspamd_rcl_add_default_handler (sub, "dns_sockets", rspamd_rcl_parse_struct_integer,
+ G_STRUCT_OFFSET (struct config_file, dns_io_per_server), RSPAMD_CL_FLAG_INT_32);
rspamd_rcl_add_default_handler (sub, "raw_mode", rspamd_rcl_parse_struct_boolean,
G_STRUCT_OFFSET (struct config_file, raw_mode), 0);
rspamd_rcl_add_default_handler (sub, "one_shot", rspamd_rcl_parse_struct_boolean,
@@ -1198,6 +1196,9 @@ rspamd_read_rcl_config (struct rspamd_rcl_section *top,
}
}
}
+ if (cur->fin) {
+ cur->fin (cfg, cur->fin_ud);
+ }
}
cfg->rcl_obj = obj;
diff --git a/src/cfg_rcl.h b/src/cfg_rcl.h
index 272272ab4..39ce2fc43 100644
--- a/src/cfg_rcl.h
+++ b/src/cfg_rcl.h
@@ -66,6 +66,13 @@ struct rspamd_rcl_struct_parser {
typedef gboolean (*rspamd_rcl_handler_t) (struct config_file *cfg, ucl_object_t *obj,
gpointer ud, struct rspamd_rcl_section *section, GError **err);
+/**
+ * A handler type that is called at the end of section parsing
+ * @param cfg configuration
+ * @param ud user data
+ */
+typedef void (*rspamd_rcl_section_fin_t)(struct config_file *cfg, gpointer ud);
+
struct rspamd_rcl_default_handler_data {
struct rspamd_rcl_struct_parser pd;
const gchar *key;
@@ -82,6 +89,8 @@ struct rspamd_rcl_section {
UT_hash_handle hh; /** hash handle */
struct rspamd_rcl_section *subsections; /**< hash table of subsections */
struct rspamd_rcl_default_handler_data *default_parser; /**< generic parsing fields */
+ rspamd_rcl_section_fin_t fin; /** called at the end of section parsing */
+ gpointer fin_ud;
};
/**
diff --git a/src/cfg_utils.c b/src/cfg_utils.c
index 765ff7f34..8e57ea1c9 100644
--- a/src/cfg_utils.c
+++ b/src/cfg_utils.c
@@ -164,7 +164,7 @@ parse_host_priority (memory_pool_t *pool, const gchar *str, gchar **addr, guint
}
gboolean
-parse_bind_line (struct config_file *cfg, struct worker_conf *cf, gchar *str)
+parse_bind_line (struct config_file *cfg, struct worker_conf *cf, const gchar *str)
{
struct rspamd_worker_bind_conf *cnf;
@@ -175,34 +175,6 @@ parse_bind_line (struct config_file *cfg, struct worker_conf *cf, gchar *str)
cnf->bind_port = DEFAULT_BIND_PORT;
if (str[0] == '/' || str[0] == '.') {
-#ifdef HAVE_DIRNAME
- /* Try to check path of bind credit */
- struct stat st;
- gint fd;
- gchar *copy = memory_pool_strdup (cfg->cfg_pool, str);
- if (stat (copy, &st) == -1) {
- if (errno == ENOENT) {
- if ((fd = open (str, O_RDWR | O_TRUNC | O_CREAT, S_IWUSR | S_IRUSR)) == -1) {
- msg_err ("cannot open path %s for making socket, %s", str, strerror (errno));
- return FALSE;
- }
- else {
- close (fd);
- unlink (str);
- }
- }
- else {
- msg_err ("cannot stat path %s for making socket, %s", str, strerror (errno));
- return 0;
- }
- }
- else {
- if (unlink (str) == -1) {
- msg_err ("cannot remove path %s for making socket, %s", str, strerror (errno));
- return 0;
- }
- }
-#endif
cnf->bind_host = memory_pool_strdup (cfg->cfg_pool, str);
cnf->is_unix = TRUE;
LL_PREPEND (cf->bind_conf, cnf);
@@ -233,6 +205,8 @@ init_defaults (struct config_file *cfg)
/* After 20 errors do throttling for 10 seconds */
cfg->dns_throttling_errors = 20;
cfg->dns_throttling_time = 10000;
+ /* 16 sockets per DNS server */
+ cfg->dns_io_per_server = 16;
cfg->statfile_sync_interval = 60000;
cfg->statfile_sync_timeout = 20000;
@@ -635,12 +609,14 @@ internal_normalizer_func (struct config_file *cfg, long double score, void *data
}
#ifdef HAVE_TANHL
return max * tanhl (score / max);
-#else
+#elif defined(HAVE_TANHL)
/*
* As some implementations of libm does not support tanhl, try to use
* tanh
*/
return max * tanh ((double) (score / max));
+#else
+ return score < max ? score / max : max;
#endif
}
@@ -776,7 +752,9 @@ rspamd_ucl_add_conf_variables (struct ucl_parser *parser)
}
gboolean
-read_rspamd_config (struct config_file *cfg, const gchar *filename, const gchar *convert_to)
+read_rspamd_config (struct config_file *cfg, const gchar *filename,
+ const gchar *convert_to, rspamd_rcl_section_fin_t logger_fin,
+ gpointer logger_ud)
{
struct stat st;
gint fd;
@@ -784,7 +762,7 @@ read_rspamd_config (struct config_file *cfg, const gchar *filename, const gchar
const gchar *ext;
GMarkupParseContext *ctx;
GError *err = NULL;
- struct rspamd_rcl_section *top;
+ struct rspamd_rcl_section *top, *logger;
gboolean res, is_xml = FALSE;
struct rspamd_xml_userdata ud;
struct ucl_parser *parser;
@@ -866,6 +844,12 @@ read_rspamd_config (struct config_file *cfg, const gchar *filename, const gchar
top = rspamd_rcl_config_init ();
err = NULL;
+ HASH_FIND_STR(top, "logging", logger);
+ if (logger != NULL) {
+ logger->fin = logger_fin;
+ logger->fin_ud = logger_ud;
+ }
+
if (!rspamd_read_rcl_config (top, cfg, cfg->rcl_obj, &err)) {
msg_err ("rcl parse error: %s", err->message);
return FALSE;
diff --git a/src/chacha_private.h b/src/chacha_private.h
new file mode 100644
index 000000000..5034f4955
--- /dev/null
+++ b/src/chacha_private.h
@@ -0,0 +1,228 @@
+/*
+chacha-merged.c version 20080118
+D. J. Bernstein
+Public domain.
+*/
+
+#ifndef CHACHA_PRIVATE_H_
+#define CHACHA_PRIVATE_H_
+
+#include <stddef.h>
+
+typedef unsigned char u8;
+typedef unsigned int u32;
+
+typedef struct
+{
+ u32 input[16]; /* could be compressed */
+} chacha_ctx;
+
+#define U8C(v) (v##U)
+#define U32C(v) (v##U)
+
+#define U8V(v) ((u8)(v) & U8C(0xFF))
+#define U32V(v) ((u32)(v) & U32C(0xFFFFFFFF))
+
+#define ROTL32(v, n) \
+ (U32V((v) << (n)) | ((v) >> (32 - (n))))
+
+#define U8TO32_LITTLE(p) \
+ (((u32)((p)[0]) ) | \
+ ((u32)((p)[1]) << 8) | \
+ ((u32)((p)[2]) << 16) | \
+ ((u32)((p)[3]) << 24))
+
+#define U32TO8_LITTLE(p, v) \
+ do { \
+ (p)[0] = U8V((v) ); \
+ (p)[1] = U8V((v) >> 8); \
+ (p)[2] = U8V((v) >> 16); \
+ (p)[3] = U8V((v) >> 24); \
+ } while (0)
+
+#define ROTATE(v,c) (ROTL32(v,c))
+#define XOR(v,w) ((v) ^ (w))
+#define PLUS(v,w) (U32V((v) + (w)))
+#define PLUSONE(v) (PLUS((v),1))
+
+#define QUARTERROUND(a,b,c,d) \
+ a = PLUS(a,b); d = ROTATE(XOR(d,a),16); \
+ c = PLUS(c,d); b = ROTATE(XOR(b,c),12); \
+ a = PLUS(a,b); d = ROTATE(XOR(d,a), 8); \
+ c = PLUS(c,d); b = ROTATE(XOR(b,c), 7);
+
+static const char sigma[16] = "expand 32-byte k";
+static const char tau[16] = "expand 16-byte k";
+
+static void chacha_keysetup(chacha_ctx *x, const u8 *k, u32 kbits, u32 ivbits)
+{
+ const char *constants;
+
+ x->input[4] = U8TO32_LITTLE(k + 0);
+ x->input[5] = U8TO32_LITTLE(k + 4);
+ x->input[6] = U8TO32_LITTLE(k + 8);
+ x->input[7] = U8TO32_LITTLE(k + 12);
+ if (kbits == 256) { /* recommended */
+ k += 16;
+ constants = sigma;
+ }
+ else { /* kbits == 128 */
+ constants = tau;
+ }
+ x->input[8] = U8TO32_LITTLE(k + 0);
+ x->input[9] = U8TO32_LITTLE(k + 4);
+ x->input[10] = U8TO32_LITTLE(k + 8);
+ x->input[11] = U8TO32_LITTLE(k + 12);
+ x->input[0] = U8TO32_LITTLE(constants + 0);
+ x->input[1] = U8TO32_LITTLE(constants + 4);
+ x->input[2] = U8TO32_LITTLE(constants + 8);
+ x->input[3] = U8TO32_LITTLE(constants + 12);
+}
+
+static void chacha_ivsetup(chacha_ctx *x, const u8 *iv)
+{
+ x->input[12] = 0;
+ x->input[13] = 0;
+ x->input[14] = U8TO32_LITTLE(iv + 0);
+ x->input[15] = U8TO32_LITTLE(iv + 4);
+}
+
+static void chacha_encrypt_bytes(chacha_ctx *x, const u8 *m, u8 *c, u32 bytes)
+{
+ u32 x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15;
+ u32 j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
+ u8 *ctarget = NULL;
+ u8 tmp[64];
+ unsigned int i;
+
+ if (!bytes)
+ return;
+
+ j0 = x->input[0];
+ j1 = x->input[1];
+ j2 = x->input[2];
+ j3 = x->input[3];
+ j4 = x->input[4];
+ j5 = x->input[5];
+ j6 = x->input[6];
+ j7 = x->input[7];
+ j8 = x->input[8];
+ j9 = x->input[9];
+ j10 = x->input[10];
+ j11 = x->input[11];
+ j12 = x->input[12];
+ j13 = x->input[13];
+ j14 = x->input[14];
+ j15 = x->input[15];
+
+ for (;;) {
+ if (bytes < 64) {
+ for (i = 0; i < bytes; ++i)
+ tmp[i] = m[i];
+ m = tmp;
+ ctarget = c;
+ c = tmp;
+ }
+ x0 = j0;
+ x1 = j1;
+ x2 = j2;
+ x3 = j3;
+ x4 = j4;
+ x5 = j5;
+ x6 = j6;
+ x7 = j7;
+ x8 = j8;
+ x9 = j9;
+ x10 = j10;
+ x11 = j11;
+ x12 = j12;
+ x13 = j13;
+ x14 = j14;
+ x15 = j15;
+ for (i = 20; i > 0; i -= 2) {
+ QUARTERROUND( x0, x4, x8, x12)
+ QUARTERROUND( x1, x5, x9, x13)
+ QUARTERROUND( x2, x6, x10, x14)
+ QUARTERROUND( x3, x7, x11, x15)
+ QUARTERROUND( x0, x5, x10, x15)
+ QUARTERROUND( x1, x6, x11, x12)
+ QUARTERROUND( x2, x7, x8, x13)
+ QUARTERROUND( x3, x4, x9, x14)
+ }
+ x0 = PLUS(x0,j0);
+ x1 = PLUS(x1,j1);
+ x2 = PLUS(x2,j2);
+ x3 = PLUS(x3,j3);
+ x4 = PLUS(x4,j4);
+ x5 = PLUS(x5,j5);
+ x6 = PLUS(x6,j6);
+ x7 = PLUS(x7,j7);
+ x8 = PLUS(x8,j8);
+ x9 = PLUS(x9,j9);
+ x10 = PLUS(x10,j10);
+ x11 = PLUS(x11,j11);
+ x12 = PLUS(x12,j12);
+ x13 = PLUS(x13,j13);
+ x14 = PLUS(x14,j14);
+ x15 = PLUS(x15,j15);
+
+#ifndef KEYSTREAM_ONLY
+ x0 = XOR(x0,U8TO32_LITTLE(m + 0));
+ x1 = XOR(x1,U8TO32_LITTLE(m + 4));
+ x2 = XOR(x2,U8TO32_LITTLE(m + 8));
+ x3 = XOR(x3,U8TO32_LITTLE(m + 12));
+ x4 = XOR(x4,U8TO32_LITTLE(m + 16));
+ x5 = XOR(x5,U8TO32_LITTLE(m + 20));
+ x6 = XOR(x6,U8TO32_LITTLE(m + 24));
+ x7 = XOR(x7,U8TO32_LITTLE(m + 28));
+ x8 = XOR(x8,U8TO32_LITTLE(m + 32));
+ x9 = XOR(x9,U8TO32_LITTLE(m + 36));
+ x10 = XOR(x10,U8TO32_LITTLE(m + 40));
+ x11 = XOR(x11,U8TO32_LITTLE(m + 44));
+ x12 = XOR(x12,U8TO32_LITTLE(m + 48));
+ x13 = XOR(x13,U8TO32_LITTLE(m + 52));
+ x14 = XOR(x14,U8TO32_LITTLE(m + 56));
+ x15 = XOR(x15,U8TO32_LITTLE(m + 60));
+#endif
+
+ j12 = PLUSONE(j12);
+ if (!j12) {
+ j13 = PLUSONE(j13);
+ /* stopping at 2^70 bytes per nonce is user's responsibility */
+ }
+
+ U32TO8_LITTLE(c + 0, x0);
+ U32TO8_LITTLE(c + 4, x1);
+ U32TO8_LITTLE(c + 8, x2);
+ U32TO8_LITTLE(c + 12, x3);
+ U32TO8_LITTLE(c + 16, x4);
+ U32TO8_LITTLE(c + 20, x5);
+ U32TO8_LITTLE(c + 24, x6);
+ U32TO8_LITTLE(c + 28, x7);
+ U32TO8_LITTLE(c + 32, x8);
+ U32TO8_LITTLE(c + 36, x9);
+ U32TO8_LITTLE(c + 40, x10);
+ U32TO8_LITTLE(c + 44, x11);
+ U32TO8_LITTLE(c + 48, x12);
+ U32TO8_LITTLE(c + 52, x13);
+ U32TO8_LITTLE(c + 56, x14);
+ U32TO8_LITTLE(c + 60, x15);
+
+ if (bytes <= 64) {
+ if (bytes < 64) {
+ for (i = 0; i < bytes; ++i)
+ ctarget[i] = c[i];
+ }
+ x->input[12] = j12;
+ x->input[13] = j13;
+ return;
+ }
+ bytes -= 64;
+ c += 64;
+#ifndef KEYSTREAM_ONLY
+ m += 64;
+#endif
+ }
+}
+
+#endif /* CHACHA_PRIVATE_H_ */
diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt
index b8430db7f..1d48a202f 100644
--- a/src/client/CMakeLists.txt
+++ b/src/client/CMakeLists.txt
@@ -4,17 +4,18 @@ SET(RSPAMCSRC rspamc.c)
ADD_EXECUTABLE(rspamc ${RSPAMCSRC})
SET_TARGET_PROPERTIES(rspamc PROPERTIES COMPILE_FLAGS "-I${CMAKE_SOURCE_DIR}/lib")
TARGET_LINK_LIBRARIES(rspamc rspamd-util)
-TARGET_LINK_LIBRARIES(rspamc rspamdclient)
-TARGET_LINK_LIBRARIES(rspamc pcre)
+IF(ENABLE_STATIC MATCHES "ON")
+ TARGET_LINK_LIBRARIES(rspamc rspamdclient_static)
+ELSE(ENABLE_STATIC MATCHES "ON")
+ TARGET_LINK_LIBRARIES(rspamc rspamdclient)
+ENDIF(ENABLE_STATIC MATCHES "ON")
IF(GLIB_COMPAT)
TARGET_LINK_LIBRARIES(rspamc glibadditions)
ENDIF(GLIB_COMPAT)
IF(OPENSSL_FOUND)
TARGET_LINK_LIBRARIES(rspamc ${OPENSSL_LIBRARIES})
ENDIF(OPENSSL_FOUND)
-TARGET_LINK_LIBRARIES(rspamc ${GLIB2_LIBRARIES})
-TARGET_LINK_LIBRARIES(rspamc ${CMAKE_REQUIRED_LIBRARIES})
-TARGET_LINK_LIBRARIES(rspamc m)
+TARGET_LINK_LIBRARIES(rspamc ${RSPAMD_REQUIRED_LIBRARIES})
IF(NOT DEBIAN_BUILD)
SET_TARGET_PROPERTIES(rspamc PROPERTIES VERSION ${RSPAMD_VERSION})
ENDIF(NOT DEBIAN_BUILD)
diff --git a/src/client/rspamc.c b/src/client/rspamc.c
index 9279682e4..3ed9f1dd4 100644
--- a/src/client/rspamc.c
+++ b/src/client/rspamc.c
@@ -46,6 +46,7 @@ static gint timeout = 5;
static gboolean pass_all;
static gboolean tty = FALSE;
static gboolean verbose = FALSE;
+static gboolean print_commands = FALSE;
static struct rspamd_client *client = NULL;
static GOptionEntry entries[] =
@@ -64,6 +65,7 @@ static GOptionEntry entries[] =
{ "rcpt", 'r', 0, G_OPTION_ARG_STRING, &rcpt, "Emulate that message is for specified user", NULL },
{ "timeout", 't', 0, G_OPTION_ARG_INT, &timeout, "Timeout for waiting for a reply", NULL },
{ "bind", 'b', 0, G_OPTION_ARG_STRING, &local_addr, "Bind to specified ip address", NULL },
+ { "commands", 0, 0, G_OPTION_ARG_NONE, &print_commands, "List available commands", NULL },
{ NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL }
};
@@ -82,6 +84,92 @@ enum rspamc_command {
RSPAMC_COMMAND_ADD_ACTION
};
+struct {
+ enum rspamc_command cmd;
+ const char *name;
+ const char *description;
+ gboolean is_controller;
+ gboolean is_privileged;
+} rspamc_command_help[] = {
+ {
+ .cmd = RSPAMC_COMMAND_SYMBOLS,
+ .name = "symbols",
+ .description = "scan message and show symbols (default command)",
+ .is_controller = FALSE,
+ .is_privileged = FALSE
+ },
+ {
+ .cmd = RSPAMC_COMMAND_LEARN_SPAM,
+ .name = "learn_spam",
+ .description = "learn message as spam",
+ .is_controller = TRUE,
+ .is_privileged = TRUE
+ },
+ {
+ .cmd = RSPAMC_COMMAND_LEARN_HAM,
+ .name = "learn_ham",
+ .description = "learn message as ham",
+ .is_controller = TRUE,
+ .is_privileged = TRUE
+ },
+ {
+ .cmd = RSPAMC_COMMAND_FUZZY_ADD,
+ .name = "fuzzy_add",
+ .description = "add message to fuzzy storage (check -f and -w options for this command)",
+ .is_controller = TRUE,
+ .is_privileged = TRUE
+ },
+ {
+ .cmd = RSPAMC_COMMAND_FUZZY_DEL,
+ .name = "fuzzy_del",
+ .description = "delete message from fuzzy storage (check -f option for this command)",
+ .is_controller = TRUE,
+ .is_privileged = TRUE
+ },
+ {
+ .cmd = RSPAMC_COMMAND_STAT,
+ .name = "stat",
+ .description = "show rspamd statistics",
+ .is_controller = TRUE,
+ .is_privileged = FALSE
+ },
+ {
+ .cmd = RSPAMC_COMMAND_STAT_RESET,
+ .name = "stat_reset",
+ .description = "show and reset rspamd statistics (useful for graphs)",
+ .is_controller = TRUE,
+ .is_privileged = TRUE
+ },
+ {
+ .cmd = RSPAMC_COMMAND_COUNTERS,
+ .name = "counters",
+ .description = "display rspamd symbols statistics",
+ .is_controller = TRUE,
+ .is_privileged = FALSE
+ },
+ {
+ .cmd = RSPAMC_COMMAND_UPTIME,
+ .name = "uptime",
+ .description = "show rspamd uptime",
+ .is_controller = TRUE,
+ .is_privileged = FALSE
+ },
+ {
+ .cmd = RSPAMC_COMMAND_ADD_SYMBOL,
+ .name = "add_symbol",
+ .description = "add or modify symbol settings in rspamd",
+ .is_controller = TRUE,
+ .is_privileged = TRUE
+ },
+ {
+ .cmd = RSPAMC_COMMAND_ADD_ACTION,
+ .name = "add_action",
+ .description = "add or modify action settings",
+ .is_controller = TRUE,
+ .is_privileged = TRUE
+ }
+};
+
/*
* Parse command line
*/
@@ -151,6 +239,30 @@ check_rspamc_command (const gchar *cmd)
return RSPAMC_COMMAND_UNKNOWN;
}
+static void
+print_commands_list (void)
+{
+ guint i;
+
+ PRINT_FUNC ("Rspamc commands summary:\n");
+ for (i = 0; i < G_N_ELEMENTS (rspamc_command_help); i ++) {
+ if (tty) {
+ PRINT_FUNC (" \033[1m%10s\033[0m (%7s%1s)\t%s\n", rspamc_command_help[i].name,
+ rspamc_command_help[i].is_controller ? "control" : "normal",
+ rspamc_command_help[i].is_privileged ? "*" : "",
+ rspamc_command_help[i].description);
+ }
+ else {
+ PRINT_FUNC (" %10s (%7s%1s)\t%s\n", rspamc_command_help[i].name,
+ rspamc_command_help[i].is_controller ? "control" : "normal",
+ rspamc_command_help[i].is_privileged ? "*" : "",
+ rspamc_command_help[i].description);
+ }
+ }
+ PRINT_FUNC ("\n* is for privileged commands that may need password (see -P option)\n");
+ PRINT_FUNC ("control commands use port 11334 while normal use 11333 by default (see -h option)\n");
+}
+
/*
* Parse connect_str and add server to librspamdclient
*/
@@ -798,6 +910,13 @@ main (gint argc, gchar **argv, gchar **env)
read_cmd_line (&argc, &argv);
+ tty = isatty (STDOUT_FILENO);
+
+ if (print_commands) {
+ print_commands_list ();
+ exit (EXIT_SUCCESS);
+ }
+
if (local_addr) {
if (inet_aton (local_addr, &ina) != 0) {
client = rspamd_client_init_binded (&ina);
@@ -812,7 +931,6 @@ main (gint argc, gchar **argv, gchar **env)
}
rspamd_set_timeout (client, 1000, timeout * 1000);
- tty = isatty (STDOUT_FILENO);
/* Now read other args from argc and argv */
if (argc == 1) {
/* No args, just read stdin */
diff --git a/src/controller.c b/src/controller.c
index d59cc2972..d3f0f2855 100644
--- a/src/controller.c
+++ b/src/controller.c
@@ -221,7 +221,8 @@ free_session (void *ud)
}
static gboolean
-restful_write_reply (gint error_code, const gchar *err_message, const gchar *buf, gsize buflen, rspamd_io_dispatcher_t *d)
+restful_write_reply (gint error_code, const gchar *err_message,
+ const gchar *buf, gsize buflen, rspamd_io_dispatcher_t *d)
{
static gchar hbuf[256];
gint r;
@@ -249,6 +250,36 @@ restful_write_reply (gint error_code, const gchar *err_message, const gchar *buf
return TRUE;
}
+static gboolean
+restful_write_reply_string (gint error_code, const gchar *err_message,
+ GString *buf, rspamd_io_dispatcher_t *d)
+{
+ static gchar hbuf[256];
+ gint r;
+
+ r = rspamd_snprintf (hbuf, sizeof (hbuf),
+ "HTTP/1.0 %d %s" CRLF "Version: " RVERSION CRLF,
+ error_code, err_message ? err_message : "OK");
+ if (buf->len > 0) {
+ r += rspamd_snprintf (hbuf + r, sizeof (hbuf) - r, "Content-Length: %z" CRLF, buf->len);
+ }
+ r += rspamd_snprintf (hbuf + r, sizeof (hbuf) - r, CRLF);
+
+ if (buf != NULL) {
+ if (!rspamd_dispatcher_write (d, hbuf, r, TRUE, TRUE)) {
+ return FALSE;
+ }
+ return rspamd_dispatcher_write_string (d, buf, FALSE, TRUE);
+ }
+ else {
+ if (!rspamd_dispatcher_write (d, hbuf, r, FALSE, TRUE)) {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
static gint
check_auth (struct controller_command *cmd, struct controller_session *session)
{
@@ -428,19 +459,16 @@ process_sync_command (struct controller_session *session, gchar **args)
static gboolean
process_counters_command (struct controller_session *session)
{
- gchar out_buf[BUFSIZ];
GList *cur;
struct cache_item *item;
struct symbols_cache *cache;
- gint r;
+ GString *out;
cache = session->cfg->cache;
+ out = g_string_sized_new (BUFSIZ);
if (!session->restful) {
- r = rspamd_snprintf (out_buf, sizeof (out_buf), "Rspamd counters." CRLF);
- }
- else {
- r = 0;
+ rspamd_printf_gstring (out, "Rspamd counters:" CRLF);
}
if (cache != NULL) {
@@ -448,7 +476,7 @@ process_counters_command (struct controller_session *session)
while (cur) {
item = cur->data;
if (!item->is_callback) {
- r += rspamd_snprintf (out_buf + r, sizeof (out_buf) - r, "%s %.2f %d %.3f" CRLF,
+ rspamd_printf_gstring (out, "%s %.2f %d %.3f" CRLF,
item->s->symbol, item->s->weight,
item->s->frequency, item->s->avg_time);
}
@@ -458,7 +486,7 @@ process_counters_command (struct controller_session *session)
while (cur) {
item = cur->data;
if (!item->is_callback) {
- r += rspamd_snprintf (out_buf + r, sizeof (out_buf) - r, "%s %.2f %d %.3f" CRLF,
+ rspamd_printf_gstring (out, "%s %.2f %d %.3f" CRLF,
item->s->symbol, item->s->weight,
item->s->frequency, item->s->avg_time);
}
@@ -467,18 +495,18 @@ process_counters_command (struct controller_session *session)
}
if (!session->restful) {
- return rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE);
+ return rspamd_dispatcher_write_string (session->dispatcher, out, FALSE, TRUE);
}
else {
- return restful_write_reply (200, NULL, out_buf, r, session->dispatcher);
+ return restful_write_reply_string (200, NULL, out, session->dispatcher);
}
}
static gboolean
process_stat_command (struct controller_session *session, gboolean do_reset)
{
- gchar out_buf[BUFSIZ];
- gint r, i;
+ GString *out;
+ gint i;
guint64 used, total, rev, ham = 0, spam = 0;
time_t ti;
memory_pool_stat_t mem_st;
@@ -486,14 +514,16 @@ process_stat_command (struct controller_session *session, gboolean do_reset)
stat_file_t *statfile;
struct statfile *st;
GList *cur_cl, *cur_st;
- struct rspamd_stat *stat;
+ struct rspamd_stat *stat, stat_copy;
memory_pool_stat (&mem_st);
- stat = session->worker->srv->stat;
- r = rspamd_snprintf (out_buf, sizeof (out_buf), "Messages scanned: %ud" CRLF, stat->messages_scanned);
- if (session->worker->srv->stat->messages_scanned > 0) {
+ memcpy (&stat_copy, session->worker->srv->stat, sizeof (stat_copy));
+ stat = &stat_copy;
+ out = g_string_sized_new (BUFSIZ);
+ rspamd_printf_gstring (out, "Messages scanned: %ud" CRLF, stat->messages_scanned);
+ if (stat->messages_scanned > 0) {
for (i = METRIC_ACTION_REJECT; i <= METRIC_ACTION_NOACTION; i ++) {
- r += rspamd_snprintf (out_buf + r, sizeof (out_buf) - r, "Messages with action %s: %ud, %.2f%%" CRLF,
+ rspamd_printf_gstring (out, "Messages with action %s: %ud, %.2f%%" CRLF,
str_action_metric (i), stat->actions_stat[i],
(double)stat->actions_stat[i] / (double)stat->messages_scanned * 100.);
if (i < METRIC_ACTION_GREYLIST) {
@@ -503,26 +533,26 @@ process_stat_command (struct controller_session *session, gboolean do_reset)
ham += stat->actions_stat[i];
}
if (do_reset) {
- stat->actions_stat[i] = 0;
+ session->worker->srv->stat->actions_stat[i] = 0;
}
}
- r += rspamd_snprintf (out_buf + r, sizeof (out_buf) - r, "Messages treated as spam: %ud, %.2f%%" CRLF, spam,
+ rspamd_printf_gstring (out, "Messages treated as spam: %ud, %.2f%%" CRLF, spam,
(double)spam / (double)stat->messages_scanned * 100.);
- r += rspamd_snprintf (out_buf + r, sizeof (out_buf) - r, "Messages treated as ham: %ud, %.2f%%" CRLF, ham,
+ rspamd_printf_gstring (out, "Messages treated as ham: %ud, %.2f%%" CRLF, ham,
(double)ham / (double)stat->messages_scanned * 100.);
}
- r += rspamd_snprintf (out_buf + r, sizeof (out_buf) - r, "Messages learned: %ud" CRLF, stat->messages_learned);
- r += rspamd_snprintf (out_buf + r, sizeof (out_buf) - r, "Connections count: %ud" CRLF, stat->connections_count);
- r += rspamd_snprintf (out_buf + r, sizeof (out_buf) - r, "Control connections count: %ud" CRLF, stat->control_connections_count);
- r += rspamd_snprintf (out_buf + r, sizeof (out_buf) - r, "Pools allocated: %z" CRLF, mem_st.pools_allocated);
- r += rspamd_snprintf (out_buf + r, sizeof (out_buf) - r, "Pools freed: %z" CRLF, mem_st.pools_freed);
- r += rspamd_snprintf (out_buf + r, sizeof (out_buf) - r, "Bytes allocated: %z" CRLF, mem_st.bytes_allocated);
- r += rspamd_snprintf (out_buf + r, sizeof (out_buf) - r, "Memory chunks allocated: %z" CRLF, mem_st.chunks_allocated);
- r += rspamd_snprintf (out_buf + r, sizeof (out_buf) - r, "Shared chunks allocated: %z" CRLF, mem_st.shared_chunks_allocated);
- r += rspamd_snprintf (out_buf + r, sizeof (out_buf) - r, "Chunks freed: %z" CRLF, mem_st.chunks_freed);
- r += rspamd_snprintf (out_buf + r, sizeof (out_buf) - r, "Oversized chunks: %z" CRLF, mem_st.oversized_chunks);
- r += rspamd_snprintf (out_buf + r, sizeof (out_buf) - r, "Fuzzy hashes stored: %ud" CRLF, stat->fuzzy_hashes);
- r += rspamd_snprintf (out_buf + r, sizeof (out_buf) - r, "Fuzzy hashes expired: %ud" CRLF, stat->fuzzy_hashes_expired);
+ rspamd_printf_gstring (out, "Messages learned: %ud" CRLF, stat->messages_learned);
+ rspamd_printf_gstring (out, "Connections count: %ud" CRLF, stat->connections_count);
+ rspamd_printf_gstring (out, "Control connections count: %ud" CRLF, stat->control_connections_count);
+ rspamd_printf_gstring (out, "Pools allocated: %z" CRLF, mem_st.pools_allocated);
+ rspamd_printf_gstring (out, "Pools freed: %z" CRLF, mem_st.pools_freed);
+ rspamd_printf_gstring (out, "Bytes allocated: %z" CRLF, mem_st.bytes_allocated);
+ rspamd_printf_gstring (out, "Memory chunks allocated: %z" CRLF, mem_st.chunks_allocated);
+ rspamd_printf_gstring (out, "Shared chunks allocated: %z" CRLF, mem_st.shared_chunks_allocated);
+ rspamd_printf_gstring (out, "Chunks freed: %z" CRLF, mem_st.chunks_freed);
+ rspamd_printf_gstring (out, "Oversized chunks: %z" CRLF, mem_st.oversized_chunks);
+ rspamd_printf_gstring (out, "Fuzzy hashes stored: %ud" CRLF, stat->fuzzy_hashes);
+ rspamd_printf_gstring (out, "Fuzzy hashes expired: %ud" CRLF, stat->fuzzy_hashes_expired);
/* Now write statistics for each statfile */
cur_cl = g_list_first (session->cfg->classifiers);
while (cur_cl) {
@@ -539,14 +569,14 @@ process_stat_command (struct controller_session *session, gboolean do_reset)
statfile_get_revision (statfile, &rev, &ti);
if (total != (guint64)-1 && used != (guint64)-1) {
if (st->label) {
- r += rspamd_snprintf (out_buf + r, sizeof (out_buf) - r,
+ rspamd_printf_gstring (out,
"Statfile: %s <%s> (version %uL); length: %Hz; free blocks: %uL; total blocks: %uL; free: %.2f%%" CRLF,
st->symbol, st->label, rev, st->size,
(total - used), total,
(double)((double)(total - used) / (double)total) * 100.);
}
else {
- r += rspamd_snprintf (out_buf + r, sizeof (out_buf) - r,
+ rspamd_printf_gstring (out,
"Statfile: %s (version %uL); length: %Hz; free blocks: %uL; total blocks: %uL; free: %.2f%%" CRLF,
st->symbol, rev, st->size,
(total - used), total,
@@ -560,17 +590,17 @@ process_stat_command (struct controller_session *session, gboolean do_reset)
}
if (do_reset) {
- stat->messages_scanned = 0;
- stat->messages_learned = 0;
- stat->connections_count = 0;
- stat->control_connections_count = 0;
+ session->worker->srv->stat->messages_scanned = 0;
+ session->worker->srv->stat->messages_learned = 0;
+ session->worker->srv->stat->connections_count = 0;
+ session->worker->srv->stat->control_connections_count = 0;
}
if (!session->restful) {
- return rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE);
+ return rspamd_dispatcher_write_string (session->dispatcher, out, FALSE, TRUE);
}
else {
- return restful_write_reply (200, NULL, out_buf, r, session->dispatcher);
+ return restful_write_reply_string (200, NULL, out, session->dispatcher);
}
}
@@ -1604,10 +1634,12 @@ controller_read_socket (f_str_t * in, void *arg)
session->state = STATE_REPLY;
break;
case STATE_OTHER:
+ rspamd_dispatcher_pause (session->dispatcher);
if (session->other_handler) {
- session->other_handler (session, in);
+ if (!session->other_handler (session, in)) {
+ return FALSE;
+ }
}
- rspamd_dispatcher_pause (session->dispatcher);
break;
case STATE_WAIT:
rspamd_dispatcher_pause (session->dispatcher);
@@ -1618,9 +1650,14 @@ controller_read_socket (f_str_t * in, void *arg)
}
if (session->state == STATE_REPLY || session->state == STATE_QUIT) {
- rspamd_dispatcher_restore (session->dispatcher);
+ /* In case of normal session we restore read state, for restful session we need to terminate immediately */
+ if (!session->restful) {
+ rspamd_dispatcher_restore (session->dispatcher);
+ }
+ else {
+ return FALSE;
+ }
}
-
return TRUE;
}
diff --git a/src/dkim.c b/src/dkim.c
index c65fb59fd..d864165ac 100644
--- a/src/dkim.c
+++ b/src/dkim.c
@@ -950,37 +950,51 @@ rspamd_dkim_simple_body_step (GChecksum *ck, const gchar **start, guint remain)
static gboolean
rspamd_dkim_canonize_body (rspamd_dkim_context_t *ctx, const gchar *start, const gchar *end)
{
+ const gchar *p;
+
if (start == NULL) {
/* Empty body */
- g_checksum_update (ctx->body_hash, CRLF, sizeof (CRLF) - 1);
+ if (ctx->body_canon_type == DKIM_CANON_SIMPLE) {
+ g_checksum_update (ctx->body_hash, CRLF, sizeof (CRLF) - 1);
+ }
+ else {
+ g_checksum_update (ctx->body_hash, "", 0);
+ }
}
else {
- end --;
- while (end > start + 2) {
- if (*end == '\n' && *(end - 1) == '\r' && *(end - 2) == '\n') {
- end -= 2;
+ /* Strip extra ending CRLF */
+ p = end - 1;
+ while (p >= start + 2) {
+ if (*p == '\n' && *(p - 1) == '\r' && *(p - 2) == '\n') {
+ p -= 2;
}
- else if (*end == '\n' && *(end - 1) == '\n') {
- end --;
+ else if (*p == '\n' && *(p - 1) == '\n') {
+ p --;
}
- else if (*end == '\r' && *(end - 1) == '\r') {
- end --;
+ else if (*p == '\r' && *(p - 1) == '\r') {
+ p --;
}
else {
break;
}
}
+ end = p + 1;
if (end == start || end == start + 2) {
/* Empty body */
- g_checksum_update (ctx->body_hash, CRLF, sizeof (CRLF) - 1);
+ if (ctx->body_canon_type == DKIM_CANON_SIMPLE) {
+ g_checksum_update (ctx->body_hash, CRLF, sizeof (CRLF) - 1);
+ }
+ else {
+ g_checksum_update (ctx->body_hash, "", 0);
+ }
}
else {
if (ctx->body_canon_type == DKIM_CANON_SIMPLE) {
/* Simple canonization */
- while (rspamd_dkim_simple_body_step (ctx->body_hash, &start, end - start + 1));
+ while (rspamd_dkim_simple_body_step (ctx->body_hash, &start, end - start));
}
else {
- while (rspamd_dkim_relaxed_body_step (ctx->body_hash, &start, end - start + 1));
+ while (rspamd_dkim_relaxed_body_step (ctx->body_hash, &start, end - start));
}
}
return TRUE;
diff --git a/src/dns.c b/src/dns.c
index bc5229532..cf70a6a86 100644
--- a/src/dns.c
+++ b/src/dns.c
@@ -1,6 +1,5 @@
/*
- * Copyright (c) 2009-2012, Vsevolod Stakhov
- * Copyright (c) 2008, 2009, 2010 William Ahern
+ * Copyright (c) 2009-2013, Vsevolod Stakhov
*
* All rights reserved.
*
@@ -24,15 +23,11 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-/*
- * Rspamd resolver library is based on code written by William Ahern.
- *
- * The original library can be found at: http://25thandclement.com/~william/projects/dns.c.html
- */
-
#include "config.h"
#include "dns.h"
#include "main.h"
+#include "utlist.h"
+#include "chacha_private.h"
#ifdef HAVE_OPENSSL
#include <openssl/rand.h>
#endif
@@ -52,19 +47,70 @@ static const unsigned initial_bias = 72;
static const gint dns_port = 53;
-
-#ifdef HAVE_ARC4RANDOM
-#define DNS_RANDOM arc4random
-#elif defined HAVE_RANDOM
-#define DNS_RANDOM random
-#else
-#define DNS_RANDOM rand
-#endif
-
#define UDP_PACKET_SIZE 4096
#define DNS_COMPRESSION_BITS 0xC0
+static void dns_retransmit_handler (gint fd, short what, void *arg);
+
+/*
+ * DNS permutor utilities
+ */
+
+#define PERMUTOR_BUF_SIZE 32768
+#define PERMUTOR_KSIZE 32
+#define PERMUTOR_IVSIZE 8
+
+struct dns_permutor {
+ chacha_ctx ctx;
+ guchar perm_buf[PERMUTOR_BUF_SIZE];
+ guint pos;
+};
+
+/**
+ * Init chacha20 context
+ * @param p
+ */
+static void
+dns_permutor_init (struct dns_permutor *p)
+{
+ /* Init random key and IV */
+ rspamd_random_bytes (p->perm_buf, sizeof (p->perm_buf));
+
+ /* Setup ctx */
+ chacha_keysetup (&p->ctx, p->perm_buf, PERMUTOR_KSIZE * 8, 0);
+ chacha_ivsetup (&p->ctx, p->perm_buf + PERMUTOR_KSIZE);
+
+ chacha_encrypt_bytes (&p->ctx, p->perm_buf, p->perm_buf, sizeof (p->perm_buf));
+
+ p->pos = 0;
+}
+
+static struct dns_permutor *
+dns_permutor_new (memory_pool_t *pool)
+{
+ struct dns_permutor *new;
+
+ new = memory_pool_alloc0 (pool, sizeof (struct dns_permutor));
+ dns_permutor_init (new);
+
+ return new;
+}
+
+static guint16
+dns_permutor_generate_id (struct dns_permutor *p)
+{
+ guint16 id;
+ if (p->pos + sizeof (guint16) >= sizeof (p->perm_buf)) {
+ dns_permutor_init (p);
+ }
+
+ memcpy (&id, &p->perm_buf[p->pos], sizeof (guint16));
+ p->pos += sizeof (guint16);
+
+ return id;
+}
+
/* Punycode utility */
static guint digit(unsigned n)
{
@@ -200,240 +246,6 @@ punycode_label_toascii(const gunichar *in, gsize in_len, gchar *out,
return TRUE;
}
-
-/*
- * P E R M U T A T I O N G E N E R A T O R
- */
-
-#define DNS_K_TEA_BLOCK_SIZE 8
-#define DNS_K_TEA_CYCLES 32
-#define DNS_K_TEA_MAGIC 0x9E3779B9U
-
-static void dns_retransmit_handler (gint fd, short what, void *arg);
-
-
-static void
-dns_k_tea_init(struct dns_k_tea *tea, guint32 key[], guint cycles)
-{
- memcpy(tea->key, key, sizeof tea->key);
-
- tea->cycles = (cycles)? cycles : DNS_K_TEA_CYCLES;
-} /* dns_k_tea_init() */
-
-
-static void
-dns_k_tea_encrypt (struct dns_k_tea *tea, guint32 v[], guint32 *w)
-{
- guint32 y, z, sum, n;
-
- y = v[0];
- z = v[1];
- sum = 0;
-
- for (n = 0; n < tea->cycles; n++) {
- sum += DNS_K_TEA_MAGIC;
- y += ((z << 4) + tea->key[0]) ^ (z + sum) ^ ((z >> 5) + tea->key[1]);
- z += ((y << 4) + tea->key[2]) ^ (y + sum) ^ ((y >> 5) + tea->key[3]);
- }
-
- w[0] = y;
- w[1] = z;
-
-} /* dns_k_tea_encrypt() */
-
-
-/*
- * Permutation generator, based on a Luby-Rackoff Feistel construction.
- *
- * Specifically, this is a generic balanced Feistel block cipher using TEA
- * (another block cipher) as the pseudo-random function, F. At best it's as
- * strong as F (TEA), notwithstanding the seeding. F could be AES, SHA-1, or
- * perhaps Bernstein's Salsa20 core; I am naively trying to keep things
- * simple.
- *
- * The generator can create a permutation of any set of numbers, as long as
- * the size of the set is an even power of 2. This limitation arises either
- * out of an inherent property of balanced Feistel constructions, or by my
- * own ignorance. I'll tackle an unbalanced construction after I wrap my
- * head around Schneier and Kelsey's paper.
- *
- * CAVEAT EMPTOR. IANAC.
- */
-#define DNS_K_PERMUTOR_ROUNDS 8
-
-
-
-static inline guint
-dns_k_permutor_powof (guint n)
-{
- guint m, i = 0;
-
- for (m = 1; m < n; m <<= 1, i++);
-
- return i;
-} /* dns_k_permutor_powof() */
-
-static void
-dns_k_permutor_init (struct dns_k_permutor *p, guint low, guint high)
-{
- guint32 key[DNS_K_TEA_KEY_SIZE / sizeof (guint32)];
- guint width, i;
-
- p->stepi = 0;
-
- p->length = (high - low) + 1;
- p->limit = high;
-
- width = dns_k_permutor_powof (p->length);
- width += width % 2;
-
- p->shift = width / 2;
- p->mask = (1U << p->shift) - 1;
- p->rounds = DNS_K_PERMUTOR_ROUNDS;
-
-#ifndef HAVE_OPENSSL
- for (i = 0; i < G_N_ELEMENTS (key); i++) {
- key[i] = DNS_RANDOM ();
- }
-#else
- if (RAND_bytes ((unsigned char *)key, sizeof (key)) != 1) {
- for (i = 0; i < G_N_ELEMENTS (key); i++) {
- key[i] = DNS_RANDOM ();
- }
- }
-#endif
- dns_k_tea_init (&p->tea, key, 0);
-
-} /* dns_k_permutor_init() */
-
-
-static guint
-dns_k_permutor_F (struct dns_k_permutor *p, guint k, guint x)
-{
- guint32 in[DNS_K_TEA_BLOCK_SIZE / sizeof (guint32)], out[DNS_K_TEA_BLOCK_SIZE / sizeof (guint32)];
-
- memset(in, '\0', sizeof in);
-
- in[0] = k;
- in[1] = x;
-
- dns_k_tea_encrypt (&p->tea, in, out);
-
- return p->mask & out[0];
-} /* dns_k_permutor_F() */
-
-
-static guint
-dns_k_permutor_E (struct dns_k_permutor *p, guint n)
-{
- guint l[2], r[2];
- guint i;
-
- i = 0;
- l[i] = p->mask & (n >> p->shift);
- r[i] = p->mask & (n >> 0);
-
- do {
- l[(i + 1) % 2] = r[i % 2];
- r[(i + 1) % 2] = l[i % 2] ^ dns_k_permutor_F(p, i, r[i % 2]);
-
- i++;
- } while (i < p->rounds - 1);
-
- return ((l[i % 2] & p->mask) << p->shift) | ((r[i % 2] & p->mask) << 0);
-} /* dns_k_permutor_E() */
-
-
-static guint
-dns_k_permutor_D (struct dns_k_permutor *p, guint n)
-{
- guint l[2], r[2];
- guint i;
-
- i = p->rounds - 1;
- l[i % 2] = p->mask & (n >> p->shift);
- r[i % 2] = p->mask & (n >> 0);
-
- do {
- i--;
-
- r[i % 2] = l[(i + 1) % 2];
- l[i % 2] = r[(i + 1) % 2] ^ dns_k_permutor_F(p, i, l[(i + 1) % 2]);
- } while (i > 0);
-
- return ((l[i % 2] & p->mask) << p->shift) | ((r[i % 2] & p->mask) << 0);
-} /* dns_k_permutor_D() */
-
-
-static guint
-dns_k_permutor_step(struct dns_k_permutor *p)
-{
- guint n;
-
- do {
- n = dns_k_permutor_E(p, p->stepi++);
- } while (n >= p->length);
-
- return n + (p->limit + 1 - p->length);
-} /* dns_k_permutor_step() */
-
-
-/*
- * Simple permutation box. Useful for shuffling rrsets from an iterator.
- * Uses AES s-box to provide good diffusion.
- */
-static guint16
-dns_k_shuffle16 (guint16 n, guint s)
-{
- static const guint8 sbox[256] =
- { 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5,
- 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
- 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0,
- 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
- 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc,
- 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
- 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a,
- 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
- 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0,
- 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
- 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b,
- 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
- 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85,
- 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
- 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5,
- 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
- 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17,
- 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
- 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88,
- 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
- 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c,
- 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
- 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9,
- 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
- 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6,
- 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
- 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e,
- 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
- 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94,
- 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
- 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68,
- 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 };
- guchar a, b;
- guint i;
-
- a = 0xff & (n >> 0);
- b = 0xff & (n >> 8);
-
- for (i = 0; i < 4; i++) {
- a ^= 0xff & s;
- a = sbox[a] ^ b;
- b = sbox[b] ^ a;
- s >>= 8;
- }
-
- return ((0xff00 & (a << 8)) | (0x00ff & (b << 0)));
-} /* dns_k_shuffle16() */
-
struct dns_request_key {
guint16 id;
guint16 port;
@@ -496,7 +308,7 @@ make_dns_header (struct rspamd_dns_request *req)
/* Set DNS header values */
header = (struct dns_header *)req->packet;
memset (header, 0 , sizeof (struct dns_header));
- header->qid = dns_k_permutor_step (req->resolver->permutor);
+ header->qid = dns_permutor_generate_id (req->resolver->permutor);
header->rd = 1;
header->qdcount = htons (1);
req->pos += sizeof (struct dns_header);
@@ -767,6 +579,7 @@ static gint
send_dns_request (struct rspamd_dns_request *req)
{
gint r;
+ struct rspamd_dns_server *serv = req->io->srv;
r = send (req->sock, req->packet, req->pos, 0);
if (r == -1) {
@@ -774,20 +587,23 @@ send_dns_request (struct rspamd_dns_request *req)
event_set (&req->io_event, req->sock, EV_WRITE, dns_retransmit_handler, req);
event_base_set (req->resolver->ev_base, &req->io_event);
event_add (&req->io_event, &req->tv);
- register_async_event (req->session, (event_finalizer_t)event_del, &req->io_event, g_quark_from_static_string ("dns resolver"));
+ register_async_event (req->session, (event_finalizer_t)event_del, &req->io_event,
+ g_quark_from_static_string ("dns resolver"));
return 0;
}
else {
- msg_err ("send failed: %s for server %s", strerror (errno), req->server->name);
- upstream_fail (&req->server->up, req->time);
+ msg_err ("send failed: %s for server %s", strerror (errno), serv->name);
+ upstream_fail (&serv->up, req->time);
return -1;
}
}
else if (r < req->pos) {
+ msg_err ("incomplete send over UDP socket, seems to be internal bug");
event_set (&req->io_event, req->sock, EV_WRITE, dns_retransmit_handler, req);
event_base_set (req->resolver->ev_base, &req->io_event);
event_add (&req->io_event, &req->tv);
- register_async_event (req->session, (event_finalizer_t)event_del, &req->io_event, g_quark_from_static_string ("dns resolver"));
+ register_async_event (req->session, (event_finalizer_t)event_del, &req->io_event,
+ g_quark_from_static_string ("dns resolver"));
return 0;
}
@@ -800,22 +616,24 @@ dns_fin_cb (gpointer arg)
struct rspamd_dns_request *req = arg;
event_del (&req->timer_event);
- g_hash_table_remove (req->resolver->requests, &req->id);
+ g_hash_table_remove (req->io->requests, &req->id);
}
static guint8 *
decompress_label (guint8 *begin, guint16 *len, guint16 max)
{
- guint16 offset = DNS_COMPRESSION_BITS;
- offset = (*len) ^ (offset << 8);
+ guint16 offset = (*len);
if (offset > max) {
+ msg_info ("invalid DNS compression pointer: %d max is %d", (gint)offset, (gint)max);
return NULL;
}
*len = *(begin + offset);
return begin + offset;
}
+#define UNCOMPRESS_DNS_OFFSET(p) (((*(p)) ^ DNS_COMPRESSION_BITS) << 8) + *((p) + 1)
+
static guint8 *
dns_request_reply_cmp (struct rspamd_dns_request *req, guint8 *in, gint len)
{
@@ -844,10 +662,9 @@ dns_request_reply_cmp (struct rspamd_dns_request *req, guint8 *in, gint len)
}
/* This may be compressed, so we need to decompress it */
if (len1 & DNS_COMPRESSION_BITS) {
- len1 = ((*p) << 8) + *(p + 1);
+ len1 = UNCOMPRESS_DNS_OFFSET(p);
l1 = decompress_label (in, &len1, len);
if (l1 == NULL) {
- msg_info ("invalid DNS pointer");
return NULL;
}
decompressed ++;
@@ -859,7 +676,7 @@ dns_request_reply_cmp (struct rspamd_dns_request *req, guint8 *in, gint len)
p += len1;
}
if (len2 & DNS_COMPRESSION_BITS) {
- len2 = ((*p) << 8) + *(p + 1);
+ len2 = UNCOMPRESS_DNS_OFFSET(p);
l2 = decompress_label (req->packet, &len2, len);
if (l2 == NULL) {
msg_info ("invalid DNS pointer");
@@ -899,14 +716,15 @@ dns_request_reply_cmp (struct rspamd_dns_request *req, guint8 *in, gint len)
#define MAX_RECURSION_LEVEL 10
static gboolean
-dns_parse_labels (guint8 *in, gchar **target, guint8 **pos, struct rspamd_dns_reply *rep, gint *remain, gboolean make_name)
+dns_parse_labels (guint8 *in, gchar **target, guint8 **pos, struct rspamd_dns_reply *rep,
+ gint *remain, gboolean make_name)
{
guint16 namelen = 0;
- guint8 *p = *pos, *begin = *pos, *l, *t, *end = *pos + *remain;
+ guint8 *p = *pos, *begin = *pos, *l, *t, *end = *pos + *remain, *new_pos = *pos;
guint16 llen;
- gint offset = -1;
- gint length = *remain;
+ gint length = *remain, new_remain = *remain;
gint ptrs = 0, labels = 0;
+ gboolean got_compression = FALSE;
/* First go through labels and calculate name length */
while (p - begin < length) {
@@ -916,34 +734,52 @@ dns_parse_labels (guint8 *in, gchar **target, guint8 **pos, struct rspamd_dns_re
}
llen = *p;
if (llen == 0) {
+ if (!got_compression) {
+ /* In case of compression we have already decremented the processing position */
+ new_remain -= sizeof (guint8);
+ new_pos += sizeof (guint8);
+ }
break;
}
- else if (llen & DNS_COMPRESSION_BITS) {
- ptrs ++;
- llen = ((*p) << 8) + *(p + 1);
- l = decompress_label (in, &llen, end - in);
- if (l == NULL) {
- msg_info ("invalid DNS pointer");
- return FALSE;
- }
- if (offset < 0) {
- /* Set offset strictly */
- offset = p - begin + 2;
+ else if ((llen & DNS_COMPRESSION_BITS)) {
+ if (end - p > 1) {
+ ptrs ++;
+ llen = UNCOMPRESS_DNS_OFFSET(p);
+ l = decompress_label (in, &llen, end - in);
+ if (l == NULL) {
+ msg_info ("invalid DNS pointer");
+ return FALSE;
+ }
+ if (!got_compression) {
+ /* Our label processing is finished actually */
+ new_remain -= sizeof (guint16);
+ new_pos += sizeof (guint16);
+ got_compression = TRUE;
+ }
+ if (l < in || l > begin + length) {
+ msg_warn ("invalid pointer in DNS packet");
+ return FALSE;
+ }
+ begin = l;
+ length = end - begin;
+ p = l + *l + 1;
+ namelen += *l;
+ labels ++;
}
- if (l < in || l > begin + length) {
- msg_warn ("invalid pointer in DNS packet");
+ else {
+ msg_warn ("DNS packet has incomplete compressed label, input length: %d bytes, remain: %d",
+ *remain, new_remain);
return FALSE;
}
- begin = l;
- length = end - begin;
- p = l + *l + 1;
- namelen += *l;
- labels ++;
}
else {
- namelen += *p;
- p += *p + 1;
+ namelen += llen;
+ p += llen + 1;
labels ++;
+ if (!got_compression) {
+ new_remain -= llen + 1;
+ new_pos += llen + 1;
+ }
}
}
@@ -962,7 +798,7 @@ dns_parse_labels (guint8 *in, gchar **target, guint8 **pos, struct rspamd_dns_re
break;
}
else if (llen & DNS_COMPRESSION_BITS) {
- llen = ((*p) << 8) + *(p + 1);
+ llen = UNCOMPRESS_DNS_OFFSET(p);
l = decompress_label (in, &llen, end - in);
begin = l;
length = end - begin;
@@ -980,11 +816,8 @@ dns_parse_labels (guint8 *in, gchar **target, guint8 **pos, struct rspamd_dns_re
}
*(t - 1) = '\0';
end:
- if (offset < 0) {
- offset = p - begin + 1;
- }
- *remain -= offset;
- *pos += offset;
+ *remain = new_remain;
+ *pos = new_pos;
return TRUE;
}
@@ -1004,8 +837,8 @@ dns_parse_rr (guint8 *in, union rspamd_reply_element *elt, guint8 **pos, struct
msg_info ("bad RR name");
return -1;
}
- if ((gint)(p - *pos) >= (gint)(*remain - sizeof (guint16) * 5) || *remain <= 0) {
- msg_info ("stripped dns reply");
+ if (*remain < (gint)sizeof (guint16) * 6) {
+ msg_info ("stripped dns reply: %d bytes remain", *remain);
return -1;
}
GET16 (type);
@@ -1023,6 +856,7 @@ dns_parse_rr (guint8 *in, union rspamd_reply_element *elt, guint8 **pos, struct
if (!(datalen & 0x3) && datalen <= *remain) {
memcpy (&elt->a.addr[0], p, sizeof (struct in_addr));
p += datalen;
+ *remain -= datalen;
parsed = TRUE;
}
else {
@@ -1035,11 +869,13 @@ dns_parse_rr (guint8 *in, union rspamd_reply_element *elt, guint8 **pos, struct
case DNS_T_AAAA:
if (rep->request->type != DNS_REQUEST_AAA) {
p += datalen;
+ *remain -= datalen;
}
else {
if (datalen == sizeof (struct in6_addr) && datalen <= *remain) {
memcpy (&elt->aaa.addr, p, sizeof (struct in6_addr));
p += datalen;
+ *remain -= datalen;
parsed = TRUE;
}
else {
@@ -1052,6 +888,7 @@ dns_parse_rr (guint8 *in, union rspamd_reply_element *elt, guint8 **pos, struct
case DNS_T_PTR:
if (rep->request->type != DNS_REQUEST_PTR) {
p += datalen;
+ *remain -= datalen;
}
else {
if (! dns_parse_labels (in, &elt->ptr.name, &p, rep, remain, TRUE)) {
@@ -1064,10 +901,10 @@ dns_parse_rr (guint8 *in, union rspamd_reply_element *elt, guint8 **pos, struct
case DNS_T_MX:
if (rep->request->type != DNS_REQUEST_MX) {
p += datalen;
+ *remain -= datalen;
}
else {
GET16 (elt->mx.priority);
- datalen -= sizeof (guint16);
if (! dns_parse_labels (in, &elt->mx.name, &p, rep, remain, TRUE)) {
msg_info ("invalid labels in MX record");
return -1;
@@ -1078,6 +915,7 @@ dns_parse_rr (guint8 *in, union rspamd_reply_element *elt, guint8 **pos, struct
case DNS_T_TXT:
if (rep->request->type != DNS_REQUEST_TXT) {
p += datalen;
+ *remain -= datalen;
}
else {
elt->txt.data = memory_pool_alloc (rep->request->pool, datalen + 1);
@@ -1091,6 +929,7 @@ dns_parse_rr (guint8 *in, union rspamd_reply_element *elt, guint8 **pos, struct
memcpy (elt->txt.data + copied, p + 1, txtlen);
copied += txtlen;
p += txtlen + 1;
+ *remain -= txtlen + 1;
}
else {
break;
@@ -1103,6 +942,7 @@ dns_parse_rr (guint8 *in, union rspamd_reply_element *elt, guint8 **pos, struct
case DNS_T_SPF:
if (rep->request->type != DNS_REQUEST_SPF) {
p += datalen;
+ *remain -= datalen;
}
else {
copied = 0;
@@ -1113,6 +953,7 @@ dns_parse_rr (guint8 *in, union rspamd_reply_element *elt, guint8 **pos, struct
memcpy (elt->txt.data + copied, p + 1, txtlen);
copied += txtlen;
p += txtlen + 1;
+ *remain -= txtlen + 1;
}
else {
break;
@@ -1125,6 +966,7 @@ dns_parse_rr (guint8 *in, union rspamd_reply_element *elt, guint8 **pos, struct
case DNS_T_SRV:
if (rep->request->type != DNS_REQUEST_SRV) {
p += datalen;
+ *remain -= datalen;
}
else {
if (p - *pos > (gint)(*remain - sizeof (guint16) * 3)) {
@@ -1144,13 +986,14 @@ dns_parse_rr (guint8 *in, union rspamd_reply_element *elt, guint8 **pos, struct
case DNS_T_CNAME:
/* Skip cname records */
p += datalen;
+ *remain -= datalen;
break;
default:
msg_debug ("unexpected RR type: %d", type);
p += datalen;
+ *remain -= datalen;
break;
}
- *remain -= datalen;
*pos = p;
if (parsed) {
@@ -1160,12 +1003,13 @@ dns_parse_rr (guint8 *in, union rspamd_reply_element *elt, guint8 **pos, struct
}
static gboolean
-dns_parse_reply (guint8 *in, gint r, struct rspamd_dns_resolver *resolver,
+dns_parse_reply (gint sock, guint8 *in, gint r, struct rspamd_dns_resolver *resolver,
struct rspamd_dns_request **req_out, struct rspamd_dns_reply **_rep)
{
struct dns_header *header = (struct dns_header *)in;
struct rspamd_dns_request *req;
struct rspamd_dns_reply *rep;
+ struct rspamd_dns_io_channel *ioc;
union rspamd_reply_element *elt;
guint8 *pos;
guint16 id;
@@ -1177,10 +1021,17 @@ dns_parse_reply (guint8 *in, gint r, struct rspamd_dns_resolver *resolver,
return FALSE;
}
+ /* Find io channel */
+ if ((ioc = g_hash_table_lookup (resolver->io_channels, GINT_TO_POINTER (sock))) == NULL) {
+ msg_err ("io channel is not found for this resolver: %d", sock);
+ return FALSE;
+ }
+
/* Now try to find corresponding request */
id = header->qid;
- if ((req = g_hash_table_lookup (resolver->requests, &id)) == NULL) {
+ if ((req = g_hash_table_lookup (ioc->requests, &id)) == NULL) {
/* No such requests found */
+ msg_debug ("DNS request with id %d has not been found for IO channel", (gint)id);
return FALSE;
}
*req_out = req;
@@ -1188,7 +1039,9 @@ dns_parse_reply (guint8 *in, gint r, struct rspamd_dns_resolver *resolver,
* Now we have request and query data is now at the end of header, so compare
* request QR section and reply QR section
*/
- if ((pos = dns_request_reply_cmp (req, in + sizeof (struct dns_header), r - sizeof (struct dns_header))) == NULL) {
+ if ((pos = dns_request_reply_cmp (req, in + sizeof (struct dns_header),
+ r - sizeof (struct dns_header))) == NULL) {
+ msg_debug ("DNS request with id %d is for different query, ignoring", (gint)id);
return FALSE;
}
/*
@@ -1261,12 +1114,12 @@ dns_read_cb (gint fd, short what, void *arg)
/* First read packet from socket */
r = read (fd, in, sizeof (in));
if (r > (gint)(sizeof (struct dns_header) + sizeof (struct dns_query))) {
- if (dns_parse_reply (in, r, resolver, &req, &rep)) {
+ if (dns_parse_reply (fd, in, r, resolver, &req, &rep)) {
/* Decrease errors count */
if (rep->request->resolver->errors > 0) {
rep->request->resolver->errors --;
}
- upstream_ok (&rep->request->server->up, rep->request->time);
+ upstream_ok (&rep->request->io->srv->up, rep->request->time);
rep->request->func (rep, rep->request->arg);
remove_normal_event (req->session, dns_fin_cb, req);
}
@@ -1278,73 +1131,44 @@ dns_timer_cb (gint fd, short what, void *arg)
{
struct rspamd_dns_request *req = arg;
struct rspamd_dns_reply *rep;
+ struct rspamd_dns_server *serv;
gint r;
/* Retransmit dns request */
req->retransmits ++;
+ serv = req->io->srv;
if (req->retransmits >= req->resolver->max_retransmits) {
- msg_err ("maximum number of retransmits expired for resolving %s of type %s", req->requested_name, dns_strtype (req->type));
- rep = memory_pool_alloc0 (req->pool, sizeof (struct rspamd_dns_reply));
- rep->request = req;
- rep->code = DNS_RC_SERVFAIL;
- upstream_fail (&rep->request->server->up, rep->request->time);
+ msg_err ("maximum number of retransmits expired for resolving %s of type %s",
+ req->requested_name, dns_strtype (req->type));
dns_check_throttling (req->resolver);
req->resolver->errors ++;
-
- req->func (rep, req->arg);
- remove_normal_event (req->session, dns_fin_cb, req);
-
- return;
- }
- /* Select other server */
- if (req->resolver->is_master_slave) {
- req->server = (struct rspamd_dns_server *)get_upstream_master_slave (req->resolver->servers,
- req->resolver->servers_num, sizeof (struct rspamd_dns_server),
- req->time, DEFAULT_UPSTREAM_ERROR_TIME, DEFAULT_UPSTREAM_DEAD_TIME, DEFAULT_UPSTREAM_MAXERRORS);
- }
- else {
- req->server = (struct rspamd_dns_server *)get_upstream_round_robin (req->resolver->servers,
- req->resolver->servers_num, sizeof (struct rspamd_dns_server),
- req->time, DEFAULT_UPSTREAM_ERROR_TIME, DEFAULT_UPSTREAM_DEAD_TIME, DEFAULT_UPSTREAM_MAXERRORS);
- }
- if (req->server == NULL) {
- rep = memory_pool_alloc0 (req->pool, sizeof (struct rspamd_dns_reply));
- rep->request = req;
- rep->code = DNS_RC_SERVFAIL;
-
- req->func (rep, req->arg);
- remove_normal_event (req->session, dns_fin_cb, req);
- return;
+ goto err;
}
-
- if (req->server->sock == -1) {
- req->server->sock = make_universal_socket (req->server->name, dns_port, SOCK_DGRAM, TRUE, FALSE, FALSE);
- }
- req->sock = req->server->sock;
if (req->sock == -1) {
- rep = memory_pool_alloc0 (req->pool, sizeof (struct rspamd_dns_reply));
- rep->request = req;
- rep->code = DNS_RC_SERVFAIL;
- upstream_fail (&rep->request->server->up, rep->request->time);
-
- req->func (rep, req->arg);
- remove_normal_event (req->session, dns_fin_cb, req);
-
- return;
+ goto err;
}
/* Add other retransmit event */
r = send_dns_request (req);
if (r == -1) {
- rep = memory_pool_alloc0 (req->pool, sizeof (struct rspamd_dns_reply));
- rep->request = req;
- rep->code = DNS_RC_SERVFAIL;
- upstream_fail (&rep->request->server->up, rep->request->time);
- req->func (rep, req->arg);
- remove_normal_event (req->session, dns_fin_cb, req);
- return;
+ goto err;
}
+
+ msg_debug ("retransmit DNS request with ID %d", (int)req->id);
evtimer_add (&req->timer_event, &req->tv);
+
+ return;
+err:
+ msg_debug ("error on retransmitting DNS request with ID %d", (int)req->id);
+ rep = memory_pool_alloc0 (req->pool, sizeof (struct rspamd_dns_reply));
+ rep->request = req;
+ rep->code = DNS_RC_SERVFAIL;
+ if (serv) {
+ upstream_fail (&serv->up, rep->request->time);
+ }
+ req->func (rep, req->arg);
+ remove_normal_event (req->session, dns_fin_cb, req);
+ return;
}
static void
@@ -1352,9 +1176,11 @@ dns_retransmit_handler (gint fd, short what, void *arg)
{
struct rspamd_dns_request *req = arg;
struct rspamd_dns_reply *rep;
+ struct rspamd_dns_server *serv;
gint r;
remove_normal_event (req->session, (event_finalizer_t)event_del, &req->io_event);
+ serv = req->io->srv;
if (what == EV_WRITE) {
/* Retransmit dns request */
@@ -1365,7 +1191,7 @@ dns_retransmit_handler (gint fd, short what, void *arg)
rep = memory_pool_alloc0 (req->pool, sizeof (struct rspamd_dns_reply));
rep->request = req;
rep->code = DNS_RC_SERVFAIL;
- upstream_fail (&rep->request->server->up, rep->request->time);
+ upstream_fail (&serv->up, rep->request->time);
req->resolver->errors ++;
dns_check_throttling (req->resolver);
@@ -1378,7 +1204,7 @@ dns_retransmit_handler (gint fd, short what, void *arg)
rep = memory_pool_alloc0 (req->pool, sizeof (struct rspamd_dns_reply));
rep->request = req;
rep->code = DNS_RC_SERVFAIL;
- upstream_fail (&rep->request->server->up, rep->request->time);
+ upstream_fail (&serv->up, rep->request->time);
req->func (rep, req->arg);
}
@@ -1390,8 +1216,9 @@ dns_retransmit_handler (gint fd, short what, void *arg)
evtimer_add (&req->timer_event, &req->tv);
/* Add request to hash table */
- g_hash_table_insert (req->resolver->requests, &req->id, req);
- register_async_event (req->session, (event_finalizer_t)dns_fin_cb, req, g_quark_from_static_string ("dns resolver"));
+ g_hash_table_insert (req->io->requests, &req->id, req);
+ register_async_event (req->session, (event_finalizer_t)dns_fin_cb,
+ req, g_quark_from_static_string ("dns resolver"));
}
}
}
@@ -1403,11 +1230,17 @@ make_dns_request (struct rspamd_dns_resolver *resolver,
{
va_list args;
struct rspamd_dns_request *req;
+ struct rspamd_dns_server *serv;
struct in_addr *addr;
const gchar *name, *service, *proto;
gint r;
+ const gint max_id_cycles = 32;
struct dns_header *header;
+ /* If no DNS servers defined silently return FALSE */
+ if (resolver->servers_num == 0) {
+ return FALSE;
+ }
/* Check throttling */
if (resolver->throttling) {
return FALSE;
@@ -1464,24 +1297,29 @@ make_dns_request (struct rspamd_dns_resolver *resolver,
req->retransmits = 0;
req->time = time (NULL);
if (resolver->is_master_slave) {
- req->server = (struct rspamd_dns_server *)get_upstream_master_slave (resolver->servers,
+ serv = (struct rspamd_dns_server *)get_upstream_master_slave (resolver->servers,
resolver->servers_num, sizeof (struct rspamd_dns_server),
req->time, DEFAULT_UPSTREAM_ERROR_TIME, DEFAULT_UPSTREAM_DEAD_TIME, DEFAULT_UPSTREAM_MAXERRORS);
}
else {
- req->server = (struct rspamd_dns_server *)get_upstream_round_robin (resolver->servers,
+ serv = (struct rspamd_dns_server *)get_upstream_round_robin (resolver->servers,
resolver->servers_num, sizeof (struct rspamd_dns_server),
req->time, DEFAULT_UPSTREAM_ERROR_TIME, DEFAULT_UPSTREAM_DEAD_TIME, DEFAULT_UPSTREAM_MAXERRORS);
}
- if (req->server == NULL) {
+ if (serv == NULL) {
msg_err ("cannot find suitable server for request");
return FALSE;
}
- if (req->server->sock == -1) {
- req->server->sock = make_universal_socket (req->server->name, dns_port, SOCK_DGRAM, TRUE, FALSE, FALSE);
+ /* Now select IO channel */
+
+ req->io = serv->cur_io_channel;
+ if (req->io == NULL) {
+ msg_err ("cannot find suitable io channel for the server %s", serv->name);
+ return FALSE;
}
- req->sock = req->server->sock;
+ serv->cur_io_channel = serv->cur_io_channel->next;
+ req->sock = req->io->sock;
if (req->sock == -1) {
return FALSE;
@@ -1496,18 +1334,23 @@ make_dns_request (struct rspamd_dns_resolver *resolver,
r = send_dns_request (req);
if (r == 1) {
- /* Add timer event */
- evtimer_add (&req->timer_event, &req->tv);
-
/* Add request to hash table */
- while (g_hash_table_lookup (resolver->requests, &req->id)) {
+ r = 0;
+ while (g_hash_table_lookup (req->io->requests, &req->id)) {
/* Check for unique id */
header = (struct dns_header *)req->packet;
- header->qid = dns_k_permutor_step (resolver->permutor);
+ header->qid = dns_permutor_generate_id (resolver->permutor);
req->id = header->qid;
+ if (++r > max_id_cycles) {
+ msg_err ("cannot generate new id for server %s", serv->name);
+ return FALSE;
+ }
}
- g_hash_table_insert (resolver->requests, &req->id, req);
- register_async_event (session, (event_finalizer_t)dns_fin_cb, req, g_quark_from_static_string ("dns resolver"));
+ /* Add timer event */
+ evtimer_add (&req->timer_event, &req->tv);
+ g_hash_table_insert (req->io->requests, &req->id, req);
+ register_async_event (session, (event_finalizer_t)dns_fin_cb, req,
+ g_quark_from_static_string ("dns resolver"));
}
else if (r == -1) {
return FALSE;
@@ -1584,14 +1427,14 @@ dns_resolver_init (struct event_base *ev_base, struct config_file *cfg)
GList *cur;
struct rspamd_dns_resolver *new;
gchar *begin, *p, *err, addr_holder[16];
- gint priority, i;
+ gint priority, i, j;
struct rspamd_dns_server *serv;
+ struct rspamd_dns_io_channel *ioc;
new = memory_pool_alloc0 (cfg->cfg_pool, sizeof (struct rspamd_dns_resolver));
new->ev_base = ev_base;
- new->requests = g_hash_table_new (dns_id_hash, dns_id_equal);
- new->permutor = memory_pool_alloc (cfg->cfg_pool, sizeof (struct dns_k_permutor));
- dns_k_permutor_init (new->permutor, 0, G_MAXUINT16);
+ new->permutor = dns_permutor_new (cfg->cfg_pool);
+ new->io_channels = g_hash_table_new (g_direct_hash, g_direct_equal);
new->static_pool = cfg->cfg_pool;
new->request_timeout = cfg->dns_timeout;
new->max_retransmits = cfg->dns_retransmits;
@@ -1602,7 +1445,7 @@ dns_resolver_init (struct event_base *ev_base, struct config_file *cfg)
/* Parse resolv.conf */
if (! parse_resolv_conf (new) || new->servers_num == 0) {
msg_err ("cannot parse resolv.conf and no nameservers defined, so no ways to resolve addresses");
- return NULL;
+ return new;
}
}
else {
@@ -1657,22 +1500,33 @@ dns_resolver_init (struct event_base *ev_base, struct config_file *cfg)
msg_err ("no valid nameservers defined, try to parse resolv.conf");
if (! parse_resolv_conf (new) || new->servers_num == 0) {
msg_err ("cannot parse resolv.conf and no nameservers defined, so no ways to resolve addresses");
- return NULL;
+ return new;
}
}
}
- /* Now init all servers */
+ /* Now init io channels to all servers */
for (i = 0; i < new->servers_num; i ++) {
serv = &new->servers[i];
- serv->sock = make_universal_socket (serv->name, dns_port, SOCK_DGRAM, TRUE, FALSE, FALSE);
- if (serv->sock == -1) {
- msg_warn ("cannot create socket to server %s", serv->name);
- }
- else {
- event_set (&serv->ev, serv->sock, EV_READ | EV_PERSIST, dns_read_cb, new);
- event_base_set (new->ev_base, &serv->ev);
- event_add (&serv->ev, NULL);
+ for (j = 0; j < (gint)cfg->dns_io_per_server; j ++) {
+ ioc = memory_pool_alloc (new->static_pool, sizeof (struct rspamd_dns_io_channel));
+ ioc->sock = make_universal_socket (serv->name, dns_port, SOCK_DGRAM, TRUE, FALSE, FALSE);
+ if (ioc->sock == -1) {
+ msg_warn ("cannot create socket to server %s", serv->name);
+ }
+ else {
+ ioc->requests = g_hash_table_new (dns_id_hash, dns_id_equal);
+ memory_pool_add_destructor (new->static_pool, (pool_destruct_func)g_hash_table_unref,
+ ioc->requests);
+ ioc->srv = serv;
+ ioc->resolver = new;
+ event_set (&ioc->ev, ioc->sock, EV_READ | EV_PERSIST, dns_read_cb, new);
+ event_base_set (new->ev_base, &ioc->ev);
+ event_add (&ioc->ev, NULL);
+ CDL_PREPEND (serv->io_channels, ioc);
+ serv->cur_io_channel = ioc;
+ g_hash_table_insert (new->io_channels, GINT_TO_POINTER (ioc->sock), ioc);
+ }
}
}
diff --git a/src/dns.h b/src/dns.h
index b04aa285f..68aa17e82 100644
--- a/src/dns.h
+++ b/src/dns.h
@@ -1,3 +1,28 @@
+/*
+ * Copyright (c) 2013, Vsevolod Stakhov
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
#ifndef RSPAMD_DNS_H
#define RSPAMD_DNS_H
@@ -17,38 +42,39 @@ struct rspamd_dns_reply;
struct config_file;
typedef void (*dns_callback_type) (struct rspamd_dns_reply *reply, gpointer arg);
+
/**
- * Implements DNS server
+ * Represents DNS server
*/
struct rspamd_dns_server {
struct upstream up; /**< upstream structure */
gchar *name; /**< name of DNS server */
+ struct rspamd_dns_io_channel *io_channels;
+ struct rspamd_dns_io_channel *cur_io_channel;
+};
+
+/**
+ * IO channel for a specific DNS server
+ */
+struct rspamd_dns_io_channel {
+ struct rspamd_dns_server *srv;
+ struct rspamd_dns_resolver *resolver;
gint sock; /**< persistent socket */
struct event ev;
+ GHashTable *requests; /**< requests in flight */
+ struct rspamd_dns_io_channel *prev, *next;
};
-#define DNS_K_TEA_KEY_SIZE 16
-
-struct dns_k_tea {
- guint32 key[DNS_K_TEA_KEY_SIZE / sizeof (guint32)];
- guint cycles;
-}; /* struct dns_k_tea */
-
-struct dns_k_permutor {
- guint stepi, length, limit;
- guint shift, mask, rounds;
-
- struct dns_k_tea tea;
-};
+struct dns_permutor;
struct rspamd_dns_resolver {
struct rspamd_dns_server servers[MAX_SERVERS];
gint servers_num; /**< number of DNS servers registered */
- GHashTable *requests; /**< requests in flight */
- struct dns_k_permutor *permutor; /**< permutor for randomizing request id */
+ struct dns_permutor *permutor; /**< permutor for randomizing request id */
guint request_timeout;
guint max_retransmits;
guint max_errors;
+ GHashTable *io_channels; /**< hash of io chains indexed by socket */
memory_pool_t *static_pool; /**< permament pool (cfg_pool) */
gboolean throttling; /**< dns servers are busy */
gboolean is_master_slave; /**< if this is true, then select upstreams as master/slave */
@@ -74,7 +100,7 @@ enum rspamd_request_type {
struct rspamd_dns_request {
memory_pool_t *pool; /**< pool associated with request */
struct rspamd_dns_resolver *resolver;
- struct rspamd_dns_server *server;
+ struct rspamd_dns_io_channel *io;
dns_callback_type func;
gpointer arg;
struct event timer_event;
@@ -234,12 +260,12 @@ struct dns_query {
/* Rspamd DNS API */
-/*
+/**
* Init DNS resolver, params are obtained from a config file or system file /etc/resolv.conf
*/
struct rspamd_dns_resolver *dns_resolver_init (struct event_base *ev_base, struct config_file *cfg);
-/*
+/**
* Make a DNS request
* @param resolver resolver object
* @param session async session to register event
@@ -254,12 +280,12 @@ gboolean make_dns_request (struct rspamd_dns_resolver *resolver,
struct rspamd_async_session *session, memory_pool_t *pool, dns_callback_type cb,
gpointer ud, enum rspamd_request_type type, ...);
-/*
+/**
* Get textual presentation of DNS error code
*/
const gchar *dns_strerror (enum dns_rcode rcode);
-/*
+/**
* Get textual presentation of DNS request type
*/
const gchar *dns_strtype (enum rspamd_request_type type);
diff --git a/src/filter.c b/src/filter.c
index c62a69084..b1448f173 100644
--- a/src/filter.c
+++ b/src/filter.c
@@ -89,7 +89,7 @@ insert_metric_result (struct worker_task *task, struct metric *metric, const gch
weight = g_hash_table_lookup (metric->symbols, symbol);
if (weight == NULL) {
- w = 1.0 * flag;
+ w = 0.0;
}
else {
w = (*weight) * flag;
@@ -116,12 +116,16 @@ insert_metric_result (struct worker_task *task, struct metric *metric, const gch
w *= metric_res->grow_factor;
metric_res->grow_factor *= metric->grow_factor;
}
- else if (w > 0) {
- metric_res->grow_factor = metric->grow_factor;
- }
s->score += w;
metric_res->score += w;
}
+ else {
+ if (fabs (s->score) < fabs (w)) {
+ /* Replace less weight with a bigger one */
+ metric_res->score = metric_res->score - s->score + w;
+ s->score = w;
+ }
+ }
}
else {
s = memory_pool_alloc (task->task_pool, sizeof (struct symbol));
diff --git a/src/fuzzy_storage.c b/src/fuzzy_storage.c
index 1f47b0dde..9bd4d49d3 100644
--- a/src/fuzzy_storage.c
+++ b/src/fuzzy_storage.c
@@ -63,6 +63,8 @@
/* Current version of fuzzy hash file format */
#define CURRENT_FUZZY_VERSION 1
+#define INVALID_NODE_TIME (guint64)-1
+
/* Init functions */
gpointer init_fuzzy (struct config_file *cfg);
void start_fuzzy (struct rspamd_worker *worker);
@@ -96,7 +98,7 @@ static sig_atomic_t wanna_die = 0;
struct rspamd_fuzzy_storage_ctx {
gboolean use_judy;
char *hashfile;
- guint32 expire;
+ gdouble expire;
guint32 frequent_score;
guint32 max_mods;
radix_tree_t *update_ips;
@@ -121,6 +123,7 @@ struct fuzzy_session {
gint fd;
u_char *pos;
socklen_t salen;
+ guint64 time;
union {
struct sockaddr ss;
struct sockaddr_storage sa;
@@ -173,23 +176,15 @@ expire_nodes (gpointer *to_expire, gint expired_num,
for (i = 0; i < expired_num; i ++) {
#ifdef WITH_JUDY
- PPvoid_t pvalue;
- gpointer data;
-
if (ctx->use_judy) {
node = (struct rspamd_fuzzy_node *)to_expire[i];
+ if (node->time != INVALID_NODE_TIME) {
+ server_stat->fuzzy_hashes_expired ++;
+ }
+ server_stat->fuzzy_hashes --;
JudySLDel (&jtree, node->h.hash_pipe, PJE0);
bloom_del (bf, node->h.hash_pipe);
g_slice_free1 (sizeof (struct rspamd_fuzzy_node), node);
-
- pvalue = JudySLGet (jtree, node->h.hash_pipe, PJE0);
- if (pvalue) {
- data = *pvalue;
- JudySLDel (&jtree, node->h.hash_pipe, PJE0);
- g_slice_free1 (sizeof (struct rspamd_fuzzy_node), data);
- server_stat->fuzzy_hashes_expired ++;
- server_stat->fuzzy_hashes --;
- }
}
else {
#endif
@@ -198,7 +193,9 @@ expire_nodes (gpointer *to_expire, gint expired_num,
head = hashes[node->h.block_size % BUCKETS];
g_queue_delete_link (head, cur);
bloom_del (bf, node->h.hash_pipe);
- server_stat->fuzzy_hashes_expired++;
+ if (node->time != INVALID_NODE_TIME) {
+ server_stat->fuzzy_hashes_expired ++;
+ }
server_stat->fuzzy_hashes--;
g_slice_free1 (sizeof(struct rspamd_fuzzy_node), node);
#ifdef WITH_JUDY
@@ -277,7 +274,7 @@ sync_cache (gpointer ud)
pvalue = JudySLFirst (jtree, indexbuf, PJE0);
while (pvalue) {
node = *((struct rspamd_fuzzy_node **)pvalue);
- if (now - node->time > expire) {
+ if (node->time == INVALID_NODE_TIME || now - node->time > expire) {
if (nodes_expired == NULL) {
nodes_expired = g_malloc (max_expired * sizeof (gpointer));
}
@@ -558,7 +555,8 @@ read_hashes_file (struct rspamd_worker *wrk)
}
static inline struct rspamd_fuzzy_node *
-check_hash_node (GQueue *hash, fuzzy_hash_t *s, gint update_value, struct rspamd_fuzzy_storage_ctx *ctx)
+check_hash_node (GQueue *hash, fuzzy_hash_t *s, gint update_value,
+ guint64 time, struct rspamd_fuzzy_storage_ctx *ctx)
{
GList *cur;
struct rspamd_fuzzy_node *h;
@@ -571,8 +569,17 @@ check_hash_node (GQueue *hash, fuzzy_hash_t *s, gint update_value, struct rspamd
pvalue = JudySLGet (jtree, s->hash_pipe, PJE0);
if (pvalue != NULL) {
h = *((struct rspamd_fuzzy_node **)pvalue);
- /* Also check block size */
- if (h->h.block_size== s->block_size) {
+
+ if (h->time == INVALID_NODE_TIME) {
+ /* Node is expired */
+ return NULL;
+ }
+ else if (update_value == 0 && time - h->time > ctx->expire) {
+ h->time = INVALID_NODE_TIME;
+ server_stat->fuzzy_hashes_expired ++;
+ return NULL;
+ }
+ else if (h->h.block_size== s->block_size) {
msg_info ("fuzzy hash was found in judy tree");
if (update_value) {
h->value += update_value;
@@ -588,10 +595,18 @@ check_hash_node (GQueue *hash, fuzzy_hash_t *s, gint update_value, struct rspamd
h = cur->data;
if ((prob = fuzzy_compare_hashes (&h->h, s)) > LEV_LIMIT) {
msg_info ("fuzzy hash was found, probability %d%%", prob);
- if (update_value) {
+ if (h->time == INVALID_NODE_TIME) {
+ return NULL;
+ }
+ else if (update_value) {
msg_info ("new hash weight: %d", h->value);
h->value += update_value;
}
+ else if (time - h->time > ctx->expire) {
+ h->time = INVALID_NODE_TIME;
+ server_stat->fuzzy_hashes_expired ++;
+ return NULL;
+ }
return h;
}
cur = g_list_next (cur);
@@ -602,9 +617,17 @@ check_hash_node (GQueue *hash, fuzzy_hash_t *s, gint update_value, struct rspamd
h = cur->data;
if ((prob = fuzzy_compare_hashes (&h->h, s)) > LEV_LIMIT) {
msg_info ("fuzzy hash was found, probability %d%%", prob);
- if (update_value) {
- h->value += update_value;
+ if (h->time == INVALID_NODE_TIME) {
+ return NULL;
+ }
+ else if (update_value) {
msg_info ("new hash weight: %d", h->value);
+ h->value += update_value;
+ }
+ else if (time - h->time > ctx->expire) {
+ h->time = INVALID_NODE_TIME;
+ server_stat->fuzzy_hashes_expired ++;
+ return NULL;
}
if (h->value > (gint)ctx->frequent_score) {
g_queue_unlink (hash, cur);
@@ -623,7 +646,7 @@ check_hash_node (GQueue *hash, fuzzy_hash_t *s, gint update_value, struct rspamd
}
static gint
-process_check_command (struct fuzzy_cmd *cmd, gint *flag, struct rspamd_fuzzy_storage_ctx *ctx)
+process_check_command (struct fuzzy_cmd *cmd, gint *flag, guint64 time, struct rspamd_fuzzy_storage_ctx *ctx)
{
fuzzy_hash_t s;
struct rspamd_fuzzy_node *h;
@@ -637,7 +660,7 @@ process_check_command (struct fuzzy_cmd *cmd, gint *flag, struct rspamd_fuzzy_st
s.block_size = cmd->blocksize;
rspamd_rwlock_reader_lock (ctx->tree_lock);
- h = check_hash_node (hashes[cmd->blocksize % BUCKETS], &s, 0, ctx);
+ h = check_hash_node (hashes[cmd->blocksize % BUCKETS], &s, 0, time, ctx);
rspamd_rwlock_reader_unlock (ctx->tree_lock);
if (h == NULL) {
@@ -650,24 +673,28 @@ process_check_command (struct fuzzy_cmd *cmd, gint *flag, struct rspamd_fuzzy_st
}
static gboolean
-update_hash (struct fuzzy_cmd *cmd, struct rspamd_fuzzy_storage_ctx *ctx)
+update_hash (struct fuzzy_cmd *cmd, guint64 time, struct rspamd_fuzzy_storage_ctx *ctx)
{
fuzzy_hash_t s;
- gboolean r;
+ struct rspamd_fuzzy_node *n;
memcpy (s.hash_pipe, cmd->hash, sizeof (s.hash_pipe));
s.block_size = cmd->blocksize;
mods ++;
rspamd_rwlock_writer_lock (ctx->tree_lock);
- r = check_hash_node (hashes[cmd->blocksize % BUCKETS], &s, cmd->value, ctx) != NULL;
+ n = check_hash_node (hashes[cmd->blocksize % BUCKETS], &s, cmd->value, time, ctx);
rspamd_rwlock_writer_unlock (ctx->tree_lock);
- return r;
+ if (n != NULL) {
+ n->time = time;
+ return TRUE;
+ }
+ return FALSE;
}
static gboolean
-process_write_command (struct fuzzy_cmd *cmd, struct rspamd_fuzzy_storage_ctx *ctx)
+process_write_command (struct fuzzy_cmd *cmd, guint64 time, struct rspamd_fuzzy_storage_ctx *ctx)
{
struct rspamd_fuzzy_node *h;
#ifdef WITH_JUDY
@@ -675,7 +702,7 @@ process_write_command (struct fuzzy_cmd *cmd, struct rspamd_fuzzy_storage_ctx *c
#endif
if (bloom_check (bf, cmd->hash)) {
- if (update_hash (cmd, ctx)) {
+ if (update_hash (cmd, time, ctx)) {
return TRUE;
}
}
@@ -684,7 +711,7 @@ process_write_command (struct fuzzy_cmd *cmd, struct rspamd_fuzzy_storage_ctx *c
h = g_slice_alloc (sizeof (struct rspamd_fuzzy_node));
memcpy (&h->h.hash_pipe, &cmd->hash, sizeof (cmd->hash));
h->h.block_size = cmd->blocksize;
- h->time = (guint64) time (NULL);
+ h->time = time;
h->value = cmd->value;
h->flag = cmd->flag;
#ifdef WITH_JUDY
@@ -764,7 +791,7 @@ delete_hash (GQueue *hash, fuzzy_hash_t *s, struct rspamd_fuzzy_storage_ctx *ctx
}
static gboolean
-process_delete_command (struct fuzzy_cmd *cmd, struct rspamd_fuzzy_storage_ctx *ctx)
+process_delete_command (struct fuzzy_cmd *cmd, guint64 time, struct rspamd_fuzzy_storage_ctx *ctx)
{
fuzzy_hash_t s;
gboolean res = FALSE;
@@ -817,7 +844,7 @@ check_fuzzy_client (struct fuzzy_session *session)
#define CMD_PROCESS(x) \
do { \
-if (process_##x##_command (&session->cmd, session->worker->ctx)) { \
+if (process_##x##_command (&session->cmd, session->time, session->worker->ctx)) { \
if (sendto (session->fd, "OK" CRLF, sizeof ("OK" CRLF) - 1, 0, &session->client_addr.ss, session->salen) == -1) { \
msg_err ("error while writing reply: %s", strerror (errno)); \
} \
@@ -837,7 +864,7 @@ process_fuzzy_command (struct fuzzy_session *session)
switch (session->cmd.cmd) {
case FUZZY_CHECK:
- r = process_check_command (&session->cmd, &flag, session->worker->ctx);
+ r = process_check_command (&session->cmd, &flag, session->time, session->worker->ctx);
if (r != 0) {
r = rspamd_snprintf (buf, sizeof (buf), "OK %d %d" CRLF, r, flag);
if (sendto (session->fd, buf, r, 0,
@@ -910,6 +937,7 @@ accept_fuzzy_socket (gint fd, short what, void *arg)
session.pos = (u_char *) & session.cmd;
session.salen = sizeof (session.client_addr);
session.ctx = worker->ctx;
+ session.time = (guint64)time (NULL);
/* Got some data */
if (what == EV_READ) {
@@ -1022,7 +1050,7 @@ init_fuzzy (struct config_file *cfg)
rspamd_rcl_register_worker_option (cfg, type, "expire",
rspamd_rcl_parse_struct_time, ctx,
- G_STRUCT_OFFSET (struct rspamd_fuzzy_storage_ctx, expire), RSPAMD_CL_FLAG_TIME_UINT_32);
+ G_STRUCT_OFFSET (struct rspamd_fuzzy_storage_ctx, expire), RSPAMD_CL_FLAG_TIME_FLOAT);
rspamd_rcl_register_worker_option (cfg, type, "use_judy",
rspamd_rcl_parse_struct_boolean, ctx,
diff --git a/src/logger.c b/src/logger.c
index 9dd999c61..c0f2e4888 100644
--- a/src/logger.c
+++ b/src/logger.c
@@ -144,6 +144,22 @@ direct_write_log_line (rspamd_logger_t *rspamd_log, void *data, gint count, gboo
}
}
+static void
+rspamd_escape_log_string (gchar *str)
+{
+ guchar *p = (guchar *)str;
+
+ while (*p) {
+ if ((*p & 0x80) || !g_ascii_isprint (*p)) {
+ *p = '?';
+ }
+ else if (*p == '\n' || *p == '\r') {
+ *p = ' ';
+ }
+ p ++;
+ }
+}
+
/* Logging utility functions */
gint
open_log_priv (rspamd_logger_t *rspamd_log, uid_t uid, gid_t gid)
@@ -259,7 +275,7 @@ reopen_log (rspamd_logger_t *logger)
* Setup logger
*/
void
-rspamd_set_logger (enum rspamd_log_type type, GQuark ptype, struct rspamd_main *rspamd)
+rspamd_set_logger (struct config_file *cfg, GQuark ptype, struct rspamd_main *rspamd)
{
gchar **strvec, *p, *err;
gint num, i, k;
@@ -271,7 +287,7 @@ rspamd_set_logger (enum rspamd_log_type type, GQuark ptype, struct rspamd_main *
memset (rspamd->logger, 0, sizeof (rspamd_logger_t));
}
- rspamd->logger->type = type;
+ rspamd->logger->type = cfg->log_type;
rspamd->logger->pid = getpid ();
rspamd->logger->process_type = ptype;
@@ -282,7 +298,7 @@ rspamd_set_logger (enum rspamd_log_type type, GQuark ptype, struct rspamd_main *
g_mutex_init (rspamd->logger->mtx);
#endif
- switch (type) {
+ switch (cfg->log_type) {
case RSPAMD_LOG_CONSOLE:
rspamd->logger->log_func = file_log_function;
rspamd->logger->fd = STDERR_FILENO;
@@ -295,7 +311,7 @@ rspamd_set_logger (enum rspamd_log_type type, GQuark ptype, struct rspamd_main *
break;
}
- rspamd->logger->cfg = rspamd->cfg;
+ rspamd->logger->cfg = cfg;
/* Set up buffer */
if (rspamd->cfg->log_buffered) {
if (rspamd->cfg->log_buf_size != 0) {
@@ -382,7 +398,7 @@ void
rspamd_common_log_function (rspamd_logger_t *rspamd_log, GLogLevelFlags log_level,
const gchar *function, const gchar *fmt, ...)
{
- static gchar logbuf[BUFSIZ], escaped_logbuf[BUFSIZ];
+ static gchar logbuf[BUFSIZ];
va_list vp;
u_char *end;
@@ -391,9 +407,9 @@ rspamd_common_log_function (rspamd_logger_t *rspamd_log, GLogLevelFlags log_leve
va_start (vp, fmt);
end = rspamd_vsnprintf (logbuf, sizeof (logbuf), fmt, vp);
*end = '\0';
- (void)rspamd_escape_string (escaped_logbuf, logbuf, sizeof (escaped_logbuf));
+ rspamd_escape_log_string (logbuf);
va_end (vp);
- rspamd_log->log_func (NULL, function, log_level, escaped_logbuf, FALSE, rspamd_log);
+ rspamd_log->log_func (NULL, function, log_level, logbuf, FALSE, rspamd_log);
g_mutex_unlock (rspamd_log->mtx);
}
}
@@ -402,7 +418,7 @@ void
rspamd_default_log_function (GLogLevelFlags log_level,
const gchar *function, const gchar *fmt, ...)
{
- static gchar logbuf[BUFSIZ], escaped_logbuf[BUFSIZ];
+ static gchar logbuf[BUFSIZ];
va_list vp;
u_char *end;
@@ -412,9 +428,9 @@ rspamd_default_log_function (GLogLevelFlags log_level,
va_start (vp, fmt);
end = rspamd_vsnprintf (logbuf, sizeof (logbuf), fmt, vp);
*end = '\0';
- (void)rspamd_escape_string (escaped_logbuf, logbuf, sizeof (escaped_logbuf));
+ rspamd_escape_log_string (logbuf);
va_end (vp);
- fprintf (stderr, "%s\n", escaped_logbuf);
+ fprintf (stderr, "%s\n", logbuf);
}
}
else if (log_level <= default_logger->cfg->log_level) {
@@ -422,9 +438,9 @@ rspamd_default_log_function (GLogLevelFlags log_level,
va_start (vp, fmt);
end = rspamd_vsnprintf (logbuf, sizeof (logbuf), fmt, vp);
*end = '\0';
- (void)rspamd_escape_string (escaped_logbuf, logbuf, sizeof (escaped_logbuf));
+ rspamd_escape_log_string (logbuf);
va_end (vp);
- default_logger->log_func (NULL, function, log_level, escaped_logbuf, FALSE, default_logger);
+ default_logger->log_func (NULL, function, log_level, logbuf, FALSE, default_logger);
g_mutex_unlock (default_logger->mtx);
}
}
@@ -687,7 +703,7 @@ file_log_function (const gchar * log_domain, const gchar *function, GLogLevelFla
void
rspamd_conditional_debug (rspamd_logger_t *rspamd_log, guint32 addr, const gchar *function, const gchar *fmt, ...)
{
- static gchar logbuf[BUFSIZ], escaped_logbuf[BUFSIZ];
+ static gchar logbuf[BUFSIZ];
va_list vp;
u_char *end;
@@ -698,9 +714,9 @@ rspamd_conditional_debug (rspamd_logger_t *rspamd_log, guint32 addr, const gchar
va_start (vp, fmt);
end = rspamd_vsnprintf (logbuf, sizeof (logbuf), fmt, vp);
*end = '\0';
- (void)rspamd_escape_string (escaped_logbuf, logbuf, sizeof (escaped_logbuf));
+ rspamd_escape_log_string (logbuf);
va_end (vp);
- rspamd_log->log_func (NULL, function, G_LOG_LEVEL_DEBUG, escaped_logbuf, TRUE, rspamd_log);
+ rspamd_log->log_func (NULL, function, G_LOG_LEVEL_DEBUG, logbuf, TRUE, rspamd_log);
g_mutex_unlock (rspamd_log->mtx);
}
}
@@ -710,13 +726,11 @@ rspamd_conditional_debug (rspamd_logger_t *rspamd_log, guint32 addr, const gchar
void
rspamd_glib_log_function (const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer arg)
{
- gchar escaped_logbuf[BUFSIZ];
rspamd_logger_t *rspamd_log = arg;
if (rspamd_log->enabled) {
g_mutex_lock (rspamd_log->mtx);
- (void)rspamd_escape_string (escaped_logbuf, message, sizeof (escaped_logbuf));
- rspamd_log->log_func (log_domain, NULL, log_level, escaped_logbuf, FALSE, rspamd_log);
+ rspamd_log->log_func (log_domain, NULL, log_level, message, FALSE, rspamd_log);
g_mutex_unlock (rspamd_log->mtx);
}
}
diff --git a/src/logger.h b/src/logger.h
index 676b30131..50b9fe41b 100644
--- a/src/logger.h
+++ b/src/logger.h
@@ -15,7 +15,7 @@ typedef struct rspamd_logger_s rspamd_logger_t;
/**
* Init logger
*/
-void rspamd_set_logger (enum rspamd_log_type type, GQuark ptype, struct rspamd_main *main);
+void rspamd_set_logger (struct config_file *cfg, GQuark ptype, struct rspamd_main *main);
/**
* Open log file or initialize other structures
*/
diff --git a/src/lua/lua_config.c b/src/lua/lua_config.c
index 48686ff03..7987fa00e 100644
--- a/src/lua/lua_config.c
+++ b/src/lua/lua_config.c
@@ -42,6 +42,7 @@ LUA_FUNCTION_DEF (config, add_hash_map);
LUA_FUNCTION_DEF (config, add_kv_map);
LUA_FUNCTION_DEF (config, get_classifier);
LUA_FUNCTION_DEF (config, register_symbol);
+LUA_FUNCTION_DEF (config, register_symbols);
LUA_FUNCTION_DEF (config, register_virtual_symbol);
LUA_FUNCTION_DEF (config, register_callback_symbol);
LUA_FUNCTION_DEF (config, register_callback_symbol_priority);
@@ -60,6 +61,7 @@ static const struct luaL_reg configlib_m[] = {
LUA_INTERFACE_DEF (config, add_kv_map),
LUA_INTERFACE_DEF (config, get_classifier),
LUA_INTERFACE_DEF (config, register_symbol),
+ LUA_INTERFACE_DEF (config, register_symbols),
LUA_INTERFACE_DEF (config, register_virtual_symbol),
LUA_INTERFACE_DEF (config, register_callback_symbol),
LUA_INTERFACE_DEF (config, register_callback_symbol_priority),
@@ -596,7 +598,52 @@ lua_config_register_symbol (lua_State * L)
}
memory_pool_add_destructor (cfg->cfg_pool, (pool_destruct_func)lua_destroy_cfg_symbol, cd);
}
- return 1;
+ return 0;
+}
+
+static gint
+lua_config_register_symbols (lua_State *L)
+{
+ struct config_file *cfg = lua_check_config (L);
+ struct lua_callback_data *cd;
+ gint i, top;
+ gchar *sym;
+ gdouble weight = 1.0;
+
+ if (lua_gettop (L) < 3) {
+ msg_err ("not enough arguments to register a function");
+ return 0;
+ }
+ if (cfg) {
+ cd = memory_pool_alloc (cfg->cfg_pool, sizeof (struct lua_callback_data));
+ if (lua_type (L, 2) == LUA_TSTRING) {
+ cd->callback.name = memory_pool_strdup (cfg->cfg_pool, luaL_checkstring (L, 2));
+ cd->cb_is_ref = FALSE;
+ }
+ else {
+ lua_pushvalue (L, 2);
+ /* Get a reference */
+ cd->callback.ref = luaL_ref (L, LUA_REGISTRYINDEX);
+ cd->cb_is_ref = TRUE;
+ }
+ if (lua_type (L, 3) == LUA_TNUMBER) {
+ weight = lua_tonumber (L, 3);
+ top = 4;
+ }
+ else {
+ top = 3;
+ }
+ sym = memory_pool_strdup (cfg->cfg_pool, luaL_checkstring (L, top));
+ cd->symbol = sym;
+ cd->L = L;
+ register_symbol (&cfg->cache, sym, weight, lua_metric_symbol_callback, cd);
+ for (i = top; i < lua_gettop (L); i ++) {
+ sym = memory_pool_strdup (cfg->cfg_pool, luaL_checkstring (L, i + 1));
+ register_virtual_symbol (&cfg->cache, sym, weight);
+ }
+ }
+
+ return 0;
}
static gint
@@ -613,7 +660,7 @@ lua_config_register_virtual_symbol (lua_State * L)
register_virtual_symbol (&cfg->cache, name, weight);
}
}
- return 1;
+ return 0;
}
static gint
@@ -645,7 +692,7 @@ lua_config_register_callback_symbol (lua_State * L)
}
memory_pool_add_destructor (cfg->cfg_pool, (pool_destruct_func)lua_destroy_cfg_symbol, cd);
}
- return 1;
+ return 0;
}
static gint
@@ -681,7 +728,7 @@ lua_config_register_callback_symbol_priority (lua_State * L)
memory_pool_add_destructor (cfg->cfg_pool, (pool_destruct_func)lua_destroy_cfg_symbol, cd);
}
- return 1;
+ return 0;
}
diff --git a/src/lua/lua_dns.c b/src/lua/lua_dns.c
index c56ff4548..540fc7713 100644
--- a/src/lua/lua_dns.c
+++ b/src/lua/lua_dns.c
@@ -33,6 +33,7 @@ LUA_FUNCTION_DEF (dns_resolver, init);
LUA_FUNCTION_DEF (dns_resolver, resolve_a);
LUA_FUNCTION_DEF (dns_resolver, resolve_ptr);
LUA_FUNCTION_DEF (dns_resolver, resolve_txt);
+LUA_FUNCTION_DEF (dns_resolver, resolve_mx);
static const struct luaL_reg dns_resolverlib_f[] = {
LUA_INTERFACE_DEF (dns_resolver, init),
@@ -43,6 +44,7 @@ static const struct luaL_reg dns_resolverlib_m[] = {
LUA_INTERFACE_DEF (dns_resolver, resolve_a),
LUA_INTERFACE_DEF (dns_resolver, resolve_ptr),
LUA_INTERFACE_DEF (dns_resolver, resolve_txt),
+ LUA_INTERFACE_DEF (dns_resolver, resolve_mx),
{"__tostring", lua_class_tostring},
{NULL, NULL}
};
@@ -128,6 +130,24 @@ lua_dns_callback (struct rspamd_dns_reply *reply, gpointer arg)
lua_pushnil (cd->L);
}
+ else if (reply->type == DNS_REQUEST_MX) {
+ lua_newtable (cd->L);
+ cur = reply->elements;
+ while (cur) {
+ elt = cur->data;
+ /* mx['name'], mx['priority'] */
+ lua_newtable (cd->L);
+ lua_set_table_index (cd->L, "name", elt->mx.name);
+ lua_pushstring (cd->L, "priority");
+ lua_pushnumber (cd->L, elt->mx.priority);
+ lua_settable (cd->L, -3);
+
+ lua_rawseti (cd->L, -2, ++i);
+ cur = g_list_next (cur);
+ }
+ lua_pushnil (cd->L);
+
+ }
else {
lua_pushnil (cd->L);
lua_pushstring (cd->L, "Unknown reply type");
@@ -288,6 +308,21 @@ lua_dns_resolver_resolve_txt (lua_State *L)
return 1;
}
+static int
+lua_dns_resolver_resolve_mx (lua_State *L)
+{
+ struct rspamd_dns_resolver *dns_resolver = lua_check_dns_resolver (L);
+
+ if (dns_resolver) {
+ return lua_dns_resolver_resolve_common (L, dns_resolver, DNS_REQUEST_MX);
+ }
+ else {
+ lua_pushnil (L);
+ }
+
+ return 1;
+}
+
gint
luaopen_dns_resolver (lua_State * L)
{
diff --git a/src/main.c b/src/main.c
index a4240aa9c..a9bf5c7e3 100644
--- a/src/main.c
+++ b/src/main.c
@@ -299,17 +299,14 @@ drop_priv (struct rspamd_main *rspamd)
}
static void
-config_logger (struct rspamd_main *rspamd, GQuark type, gboolean is_fatal)
+config_logger (struct config_file *cfg, gpointer ud)
{
- rspamd_set_logger (rspamd->cfg->log_type, type, rspamd);
- if (open_log_priv (rspamd->logger, rspamd->workers_uid, rspamd->workers_gid) == -1) {
- if (is_fatal) {
- fprintf (stderr, "Fatal error, cannot open logfile, exiting\n");
- exit (EXIT_FAILURE);
- }
- else {
- msg_err ("cannot log to file, logfile unaccessable");
- }
+ struct rspamd_main *rm = ud;
+
+ rspamd_set_logger (cfg, g_quark_try_string ("main"), rm);
+ if (open_log_priv (rm->logger, rm->workers_uid, rm->workers_gid) == -1) {
+ fprintf (stderr, "Fatal error, cannot open logfile, exiting\n");
+ exit (EXIT_FAILURE);
}
}
@@ -366,7 +363,6 @@ reread_config (struct rspamd_main *rspamd)
gchar *cfg_file;
GList *l;
struct filter *filt;
- GQuark type;
tmp_cfg = (struct config_file *)g_malloc (sizeof (struct config_file));
if (tmp_cfg) {
@@ -380,6 +376,7 @@ reread_config (struct rspamd_main *rspamd)
memory_pool_add_destructor (tmp_cfg->cfg_pool, (pool_destruct_func)lua_close, tmp_cfg->lua_state);
if (! load_rspamd_config (tmp_cfg, FALSE)) {
+ rspamd_set_logger (rspamd_main->cfg, g_quark_try_string ("main"), rspamd_main);
msg_err ("cannot parse new config file, revert to old one");
free_config (tmp_cfg);
}
@@ -393,12 +390,11 @@ reread_config (struct rspamd_main *rspamd)
if (is_debug) {
rspamd->cfg->log_level = G_LOG_LEVEL_DEBUG;
}
- type = g_quark_try_string ("main");
- config_logger (rspamd, type, FALSE);
/* Pre-init of cache */
rspamd->cfg->cache = g_new0 (struct symbols_cache, 1);
rspamd->cfg->cache->static_pool = memory_pool_new (memory_pool_get_size ());
rspamd->cfg->cache->cfg = rspamd->cfg;
+ rspamd_main->cfg->cache->items_by_symbol = g_hash_table_new (rspamd_str_hash, rspamd_str_equal);
/* Perform modules configuring */
l = g_list_first (rspamd->cfg->filters);
@@ -723,7 +719,8 @@ load_rspamd_config (struct config_file *cfg, gboolean init_modules)
struct filter *filt;
struct module_ctx *cur_module = NULL;
- if (! read_rspamd_config (cfg, cfg->cfg_name, convert_config)) {
+ if (! read_rspamd_config (cfg, cfg->cfg_name, convert_config,
+ config_logger, rspamd_main)) {
return FALSE;
}
@@ -996,10 +993,6 @@ main (gint argc, gchar **argv, gchar **env)
GList *l;
worker_t **pworker;
GQuark type;
-#ifdef HAVE_OPENSSL
- gchar rand_bytes[sizeof (guint32)];
- guint32 rand_seed;
-#endif
#ifdef HAVE_SA_SIGINFO
signals_info = g_queue_new ();
@@ -1038,7 +1031,7 @@ main (gint argc, gchar **argv, gchar **env)
rspamd_main->cfg->log_level = G_LOG_LEVEL_DEBUG;
}
else {
- rspamd_main->cfg->log_level = G_LOG_LEVEL_INFO;
+ rspamd_main->cfg->log_level = G_LOG_LEVEL_WARNING;
}
type = g_quark_from_static_string ("main");
@@ -1054,23 +1047,16 @@ main (gint argc, gchar **argv, gchar **env)
#ifdef HAVE_OPENSSL
ERR_load_crypto_strings ();
- /* Init random generator */
- if (RAND_bytes (rand_bytes, sizeof (rand_bytes)) != 1) {
- msg_err ("cannot seed random generator using openssl: %s, using time", ERR_error_string (ERR_get_error (), NULL));
- g_random_set_seed (time (NULL));
- }
- else {
- memcpy (&rand_seed, rand_bytes, sizeof (guint32));
- g_random_set_seed (rand_seed);
- }
-
OpenSSL_add_all_algorithms ();
OpenSSL_add_all_digests ();
OpenSSL_add_all_ciphers ();
#endif
+ rspamd_prng_seed ();
+
/* First set logger to console logger */
- rspamd_set_logger (RSPAMD_LOG_CONSOLE, type, rspamd_main);
+ rspamd_main->cfg->log_type = RSPAMD_LOG_CONSOLE;
+ rspamd_set_logger (rspamd_main->cfg, type, rspamd_main);
(void)open_log (rspamd_main->logger);
g_log_set_default_handler (rspamd_glib_log_function, rspamd_main->logger);
@@ -1159,8 +1145,6 @@ main (gint argc, gchar **argv, gchar **env)
rlim.rlim_cur = 100 * 1024 * 1024;
setrlimit (RLIMIT_STACK, &rlim);
- config_logger (rspamd_main, type, TRUE);
-
/* Create rolling history */
rspamd_main->history = rspamd_roll_history_new (rspamd_main->server_pool);
diff --git a/src/main.h b/src/main.h
index 29c8a9f0c..e3a13ee65 100644
--- a/src/main.h
+++ b/src/main.h
@@ -122,20 +122,11 @@ struct process_exception {
};
/**
- * Union that would be used for storing sockaddrs
- */
-union sa_union {
- struct sockaddr_storage ss;
- struct sockaddr_in s4;
- struct sockaddr_in6 s6;
-};
-
-/**
* Control session object
*/
struct controller_command;
struct controller_session;
-typedef void (*controller_func_t)(gchar **args, struct controller_session *session);
+typedef gboolean (*controller_func_t)(gchar **args, struct controller_session *session);
struct controller_session {
struct rspamd_worker *worker; /**< pointer to worker structure (controller in fact) */
@@ -168,7 +159,7 @@ struct controller_session {
f_str_t *learn_buf; /**< learn input */
GList *parts; /**< extracted mime parts */
gint in_class; /**< positive or negative learn */
- void (*other_handler)(struct controller_session *session,
+ gboolean (*other_handler)(struct controller_session *session,
f_str_t *in); /**< other command handler to execute at the end of processing */
void *other_data; /**< and its data */
controller_func_t custom_handler; /**< custom command handler */
diff --git a/src/plugins/fuzzy_check.c b/src/plugins/fuzzy_check.c
index b79ecc452..ba07acd2f 100644
--- a/src/plugins/fuzzy_check.c
+++ b/src/plugins/fuzzy_check.c
@@ -60,72 +60,81 @@
#define DEFAULT_PORT 11335
struct storage_server {
- struct upstream up;
- gchar *name;
- gchar *addr;
- guint16 port;
+ struct upstream up;
+ gchar *name;
+ gchar *addr;
+ guint16 port;
};
struct fuzzy_mapping {
- guint64 fuzzy_flag;
- const gchar *symbol;
+ guint64 fuzzy_flag;
+ const gchar *symbol;
double weight;
};
struct fuzzy_mime_type {
- gchar *type;
- gchar *subtype;
+ gchar *type;
+ gchar *subtype;
+};
+
+struct fuzzy_rule {
+ struct storage_server *servers;
+ gint servers_num;
+ const gchar *symbol;
+ GHashTable *mappings;
+ GList *mime_types;
+ double max_score;
+ gboolean read_only;
+ gboolean skip_unknown;
};
struct fuzzy_ctx {
- gint (*filter) (struct worker_task * task);
- const gchar *symbol;
- struct storage_server *servers;
- gint servers_num;
- memory_pool_t *fuzzy_pool;
- double max_score;
- guint32 min_hash_len;
- radix_tree_t *whitelist;
- GHashTable *mappings;
- GList *mime_types;
- guint32 min_bytes;
- guint32 min_height;
- guint32 min_width;
- guint32 io_timeout;
+ gint (*filter) (struct worker_task * task);
+ memory_pool_t *fuzzy_pool;
+ GList *fuzzy_rules;
+ const gchar *default_symbol;
+ guint32 min_hash_len;
+ radix_tree_t *whitelist;
+ guint32 min_bytes;
+ guint32 min_height;
+ guint32 min_width;
+ guint32 io_timeout;
};
struct fuzzy_client_session {
- gint state;
- fuzzy_hash_t *h;
- struct event ev;
- struct timeval tv;
- struct worker_task *task;
- struct storage_server *server;
- gint fd;
+ gint state;
+ fuzzy_hash_t *h;
+ struct event ev;
+ struct timeval tv;
+ struct worker_task *task;
+ struct storage_server *server;
+ struct fuzzy_rule *rule;
+ gint fd;
};
struct fuzzy_learn_session {
- struct event ev;
- fuzzy_hash_t *h;
- gint cmd;
- gint value;
- gint flag;
- gint *saved;
- GError **err;
- struct timeval tv;
- struct controller_session *session;
- struct storage_server *server;
- struct worker_task *task;
- gint fd;
+ struct event ev;
+ fuzzy_hash_t *h;
+ gint cmd;
+ gint value;
+ gint flag;
+ gint *saved;
+ GError **err;
+ struct timeval tv;
+ struct controller_session *session;
+ struct storage_server *server;
+ struct fuzzy_rule *rule;
+ struct worker_task *task;
+ gint fd;
};
-static struct fuzzy_ctx *fuzzy_module_ctx = NULL;
-static const gchar hex_digits[] = "0123456789abcdef";
+static struct fuzzy_ctx *fuzzy_module_ctx = NULL;
+static const gchar hex_digits[] = "0123456789abcdef";
-static gint fuzzy_mime_filter (struct worker_task *task);
-static void fuzzy_symbol_callback (struct worker_task *task, void *unused);
-static void fuzzy_add_handler (gchar **args, struct controller_session *session);
-static void fuzzy_delete_handler (gchar **args, struct controller_session *session);
+static void fuzzy_symbol_callback (struct worker_task *task, void *unused);
+static gboolean fuzzy_add_handler (gchar **args, struct controller_session *session);
+static gboolean fuzzy_delete_handler (gchar **args,
+ struct controller_session *session);
/* Initialization */
gint fuzzy_check_module_init (struct config_file *cfg, struct module_ctx **ctx);
@@ -139,82 +148,36 @@ module_t fuzzy_check_module = {
fuzzy_check_module_reconfig
};
-/* Flags string is in format <numeric_flag>:<SYMBOL>:weight[, <numeric_flag>:<SYMBOL>:weight...] */
static void
-parse_flags_string_old (struct config_file *cfg, const gchar *str)
-{
- gchar **strvec, *item, *err_str, **map_str;
- gint num, i, t;
- struct fuzzy_mapping *map;
-
- strvec = g_strsplit_set (str, ", ;", 0);
- num = g_strv_length (strvec);
-
- for (i = 0; i < num; i ++) {
- item = strvec[i];
- map_str = g_strsplit_set (item, ":", 3);
- t = g_strv_length (map_str);
- if (t != 3 && t != 2) {
- msg_err ("invalid fuzzy mapping: %s", item);
- }
- else {
- map = memory_pool_alloc (fuzzy_module_ctx->fuzzy_pool, sizeof (struct fuzzy_mapping));
- map->symbol = memory_pool_strdup (fuzzy_module_ctx->fuzzy_pool, map_str[1]);
-
- errno = 0;
- map->fuzzy_flag = strtol (map_str[0], &err_str, 10);
- if (errno != 0 || (err_str && *err_str != '\0')) {
- msg_info ("cannot parse flag %s: %s", map_str[0], strerror (errno));
- continue;
- }
- else if (t == 2) {
- /* Weight is skipped in definition */
- map->weight = fuzzy_module_ctx->max_score;
- }
- else {
- map->weight = strtol (map_str[2], &err_str, 10);
-
- }
- /* Add flag to hash table */
- g_hash_table_insert (fuzzy_module_ctx->mappings, GINT_TO_POINTER(map->fuzzy_flag), map);
- register_virtual_symbol (&cfg->cache, map->symbol, map->weight);
- }
- g_strfreev (map_str);
- }
-
- g_strfreev (strvec);
-}
-
-static void
-parse_flags_string (struct config_file *cfg, ucl_object_t *val)
+parse_flags (struct fuzzy_rule *rule, struct config_file *cfg, ucl_object_t *val)
{
ucl_object_t *elt;
struct fuzzy_mapping *map;
const gchar *sym = NULL;
if (val->type == UCL_STRING) {
- parse_flags_string_old (cfg, ucl_obj_tostring (val));
+ msg_err ("string mappings are deprecated and no longer supported, use new style configuration");
}
else if (val->type == UCL_OBJECT) {
- elt = ucl_obj_get_key (val, "symbol");
+ elt = ucl_object_find_key (val, "symbol");
if (elt == NULL || !ucl_object_tostring_safe (elt, &sym)) {
sym = ucl_object_key (val);
}
if (sym != NULL) {
map = memory_pool_alloc (fuzzy_module_ctx->fuzzy_pool, sizeof (struct fuzzy_mapping));
map->symbol = sym;
- elt = ucl_obj_get_key (val, "flag");
+ elt = ucl_object_find_key (val, "flag");
if (elt != NULL && ucl_obj_toint_safe (elt, &map->fuzzy_flag)) {
- elt = ucl_obj_get_key (val, "weight");
+ elt = ucl_object_find_key (val, "max_score");
if (elt != NULL) {
map->weight = ucl_obj_todouble (elt);
}
else {
- map->weight = fuzzy_module_ctx->max_score;
+ map->weight = rule->max_score;
}
/* Add flag to hash table */
- g_hash_table_insert (fuzzy_module_ctx->mappings, GINT_TO_POINTER (map->fuzzy_flag), map);
- register_virtual_symbol (&cfg->cache, map->symbol, map->weight);
+ g_hash_table_insert (rule->mappings, GINT_TO_POINTER (map->fuzzy_flag), map);
+ register_virtual_symbol (&cfg->cache, map->symbol, 1.0);
}
else {
msg_err ("fuzzy_map parameter has no flag definition");
@@ -261,12 +224,12 @@ parse_mime_types (const gchar *str)
}
static gboolean
-fuzzy_check_content_type (GMimeContentType *type)
+fuzzy_check_content_type (struct fuzzy_rule *rule, GMimeContentType *type)
{
struct fuzzy_mime_type *ft;
GList *cur;
- cur = fuzzy_module_ctx->mime_types;
+ cur = rule->mime_types;
while (cur) {
ft = cur->data;
if (g_mime_content_type_is_type (type, ft->type, ft->subtype)) {
@@ -279,7 +242,7 @@ fuzzy_check_content_type (GMimeContentType *type)
}
static void
-parse_servers_string (const gchar *str)
+parse_servers_string (struct fuzzy_rule *rule, const gchar *str)
{
gchar **strvec;
gint i, num;
@@ -288,18 +251,18 @@ parse_servers_string (const gchar *str)
strvec = g_strsplit_set (str, ",", 0);
num = g_strv_length (strvec);
- fuzzy_module_ctx->servers = memory_pool_alloc0 (fuzzy_module_ctx->fuzzy_pool, sizeof (struct storage_server) * num);
+ rule->servers = memory_pool_alloc0 (fuzzy_module_ctx->fuzzy_pool, sizeof (struct storage_server) * num);
for (i = 0; i < num; i++) {
g_strstrip (strvec[i]);
- cur = &fuzzy_module_ctx->servers[fuzzy_module_ctx->servers_num];
+ cur = &rule->servers[rule->servers_num];
if (parse_host_port (fuzzy_module_ctx->fuzzy_pool, strvec[i], &cur->addr, &cur->port)) {
if (cur->port == 0) {
cur->port = DEFAULT_PORT;
}
cur->name = memory_pool_strdup (fuzzy_module_ctx->fuzzy_pool, strvec[i]);
- fuzzy_module_ctx->servers_num++;
+ rule->servers_num++;
}
}
@@ -310,32 +273,25 @@ parse_servers_string (const gchar *str)
static double
fuzzy_normalize (gint32 in, double weight)
{
- double ms = weight, ams = fabs (ms), ain = fabs (in);
-
- if (ams > 0.001) {
- if (ain < ams / 2.) {
- return in;
- }
- else if (ain < ams * 2.) {
- ain = ain / 3. + ams / 3.;
- return in > 0 ? ain : -(ain);
- }
- else {
- return in > 0 ? ms : -(ms);
- }
+ if (weight == 0) {
+ return 0;
}
-
- return (double)in;
+#ifdef HAVE_TANH
+ return tanh (G_E * (double)in / weight);
+#else
+ return (in < weight ? in / weight : weight);
+#endif
}
static const gchar *
fuzzy_to_string (fuzzy_hash_t *h)
{
static gchar strbuf [FUZZY_HASHLEN * 2 + 1];
+ const int max_print = 5;
gint i;
guint8 byte;
- for (i = 0; i < FUZZY_HASHLEN; i ++) {
+ for (i = 0; i < max_print; i ++) {
byte = h->hash_pipe[i];
if (byte == '\0') {
break;
@@ -343,22 +299,102 @@ fuzzy_to_string (fuzzy_hash_t *h)
strbuf[i * 2] = hex_digits[byte >> 4];
strbuf[i * 2 + 1] = hex_digits[byte & 0xf];
}
-
- strbuf[i * 2] = '\0';
+ if (i == max_print) {
+ memcpy (&strbuf[i * 2], "...", 4);
+ }
+ else {
+ strbuf[i * 2] = '\0';
+ }
return strbuf;
}
+static struct fuzzy_rule *
+fuzzy_rule_new (const char *default_symbol, memory_pool_t *pool)
+{
+ struct fuzzy_rule *rule;
+
+ rule = memory_pool_alloc0 (pool, sizeof (struct fuzzy_rule));
+
+ rule->mappings = g_hash_table_new (g_direct_hash, g_direct_equal);
+ rule->symbol = default_symbol;
+ memory_pool_add_destructor (pool, (pool_destruct_func)g_hash_table_unref, rule->mappings);
+ rule->read_only = FALSE;
+
+ return rule;
+}
+
+static gint
+fuzzy_parse_rule (struct config_file *cfg, ucl_object_t *obj)
+{
+ ucl_object_t *value, *cur;
+ struct fuzzy_rule *rule;
+ ucl_object_iter_t it = NULL;
+
+ if (obj->type != UCL_OBJECT) {
+ msg_err ("invalid rule definition");
+ return -1;
+ }
+
+ rule = fuzzy_rule_new (fuzzy_module_ctx->default_symbol, fuzzy_module_ctx->fuzzy_pool);
+
+ if ((value = ucl_object_find_key (obj, "mime_types")) != NULL) {
+ if (value->type == UCL_ARRAY) {
+ value = value->value.av;
+ }
+ LL_FOREACH (value, cur) {
+ rule->mime_types = g_list_concat (rule->mime_types,
+ parse_mime_types (ucl_obj_tostring (cur)));
+ }
+ }
+
+ if ((value = ucl_object_find_key (obj, "max_score")) != NULL) {
+ rule->max_score = ucl_obj_todouble (value);
+ }
+ if ((value = ucl_object_find_key (obj, "symbol")) != NULL) {
+ rule->symbol = ucl_obj_tostring (value);
+ }
+ if ((value = ucl_object_find_key (obj, "read_only")) != NULL) {
+ rule->read_only = ucl_obj_toboolean (value);
+ }
+ if ((value = ucl_object_find_key (obj, "skip_unknown")) != NULL) {
+ rule->skip_unknown = ucl_obj_toboolean (value);
+ }
+
+ if ((value = ucl_object_find_key (obj, "servers")) != NULL) {
+ if (value->type == UCL_ARRAY) {
+ value = value->value.av;
+ }
+ LL_FOREACH (value, cur) {
+ parse_servers_string (rule, ucl_obj_tostring (cur));
+ }
+ }
+ if ((value = ucl_object_find_key (obj, "fuzzy_map")) != NULL) {
+ while ((cur = ucl_iterate_object (value, &it, true)) != NULL) {
+ parse_flags (rule, cfg, cur);
+ }
+ }
+
+ if (rule->servers_num == 0) {
+ msg_err ("no servers defined for fuzzy rule with symbol: %s", rule->symbol);
+ return -1;
+ }
+ else {
+ fuzzy_module_ctx->fuzzy_rules = g_list_prepend (fuzzy_module_ctx->fuzzy_rules, rule);
+ if (rule->symbol != fuzzy_module_ctx->default_symbol) {
+ register_virtual_symbol (&cfg->cache, rule->symbol, 1.0);
+ }
+ }
+
+ return 0;
+}
+
gint
fuzzy_check_module_init (struct config_file *cfg, struct module_ctx **ctx)
{
fuzzy_module_ctx = g_malloc0 (sizeof (struct fuzzy_ctx));
- fuzzy_module_ctx->filter = fuzzy_mime_filter;
fuzzy_module_ctx->fuzzy_pool = memory_pool_new (memory_pool_get_size ());
- fuzzy_module_ctx->servers = NULL;
- fuzzy_module_ctx->servers_num = 0;
- fuzzy_module_ctx->mappings = g_hash_table_new (g_direct_hash, g_direct_equal);
*ctx = (struct module_ctx *)fuzzy_module_ctx;
@@ -369,20 +405,13 @@ gint
fuzzy_check_module_config (struct config_file *cfg)
{
ucl_object_t *value, *cur;
- ucl_object_iter_t it = NULL;
- gint res = TRUE;
+ gint res = TRUE;
if ((value = get_module_opt (cfg, "fuzzy_check", "symbol")) != NULL) {
- fuzzy_module_ctx->symbol = ucl_obj_tostring (value);
- }
- else {
- fuzzy_module_ctx->symbol = DEFAULT_SYMBOL;
- }
- if ((value = get_module_opt (cfg, "fuzzy_check", "max_score")) != NULL) {
- fuzzy_module_ctx->max_score = ucl_obj_todouble (value);
+ fuzzy_module_ctx->default_symbol = ucl_obj_tostring (value);
}
else {
- fuzzy_module_ctx->max_score = 0.;
+ fuzzy_module_ctx->default_symbol = DEFAULT_SYMBOL;
}
if ((value = get_module_opt (cfg, "fuzzy_check", "min_length")) != NULL) {
@@ -415,11 +444,6 @@ fuzzy_check_module_config (struct config_file *cfg)
else {
fuzzy_module_ctx->io_timeout = DEFAULT_IO_TIMEOUT;
}
- if ((value = get_module_opt (cfg, "fuzzy_check", "mime_types")) != NULL) {
- LL_FOREACH (value, cur) {
- fuzzy_module_ctx->mime_types = parse_mime_types (ucl_obj_tostring (cur));
- }
- }
if ((value = get_module_opt (cfg, "fuzzy_check", "whitelist")) != NULL) {
fuzzy_module_ctx->whitelist = radix_tree_create ();
@@ -433,21 +457,24 @@ fuzzy_check_module_config (struct config_file *cfg)
fuzzy_module_ctx->whitelist = NULL;
}
- if ((value = get_module_opt (cfg, "fuzzy_check", "servers")) != NULL) {
+ if ((value = get_module_opt (cfg, "fuzzy_check", "rule")) != NULL) {
LL_FOREACH (value, cur) {
- parse_servers_string (ucl_obj_tostring (cur));
- }
- }
- if ((value = get_module_opt (cfg, "fuzzy_check", "fuzzy_map")) != NULL) {
- while ((cur = ucl_iterate_object (value, &it, true)) != NULL) {
- parse_flags_string (cfg, cur);
+ if (fuzzy_parse_rule (cfg, cur) == -1) {
+ return -1;
+ }
}
}
- register_symbol (&cfg->cache, fuzzy_module_ctx->symbol, fuzzy_module_ctx->max_score, fuzzy_symbol_callback, NULL);
+ if (fuzzy_module_ctx->fuzzy_rules != NULL) {
+ register_callback_symbol (&cfg->cache, fuzzy_module_ctx->default_symbol,
+ 1.0, fuzzy_symbol_callback, NULL);
- register_custom_controller_command ("fuzzy_add", fuzzy_add_handler, TRUE, TRUE);
- register_custom_controller_command ("fuzzy_del", fuzzy_delete_handler, TRUE, TRUE);
+ register_custom_controller_command ("fuzzy_add", fuzzy_add_handler, TRUE, TRUE);
+ register_custom_controller_command ("fuzzy_del", fuzzy_delete_handler, TRUE, TRUE);
+ }
+ else {
+ msg_warn ("fuzzy module is enabled but no rules are defined");
+ }
return res;
}
@@ -456,11 +483,9 @@ gint
fuzzy_check_module_reconfig (struct config_file *cfg)
{
memory_pool_delete (fuzzy_module_ctx->fuzzy_pool);
- fuzzy_module_ctx->servers = NULL;
- fuzzy_module_ctx->servers_num = 0;
+
fuzzy_module_ctx->fuzzy_pool = memory_pool_new (memory_pool_get_size ());
- g_hash_table_remove_all (fuzzy_module_ctx->mappings);
return fuzzy_check_module_config (cfg);
}
@@ -518,21 +543,24 @@ fuzzy_io_callback (gint fd, short what, void *arg)
}
*err_str = '\0';
/* Get mapping by flag */
- if ((map = g_hash_table_lookup (fuzzy_module_ctx->mappings, GINT_TO_POINTER (flag))) == NULL) {
+ if ((map = g_hash_table_lookup (session->rule->mappings, GINT_TO_POINTER (flag))) == NULL) {
/* Default symbol and default weight */
- symbol = fuzzy_module_ctx->symbol;
- nval = fuzzy_normalize (value, fuzzy_module_ctx->max_score);
+ symbol = session->rule->symbol;
+ nval = fuzzy_normalize (value, session->rule->max_score);
}
else {
/* Get symbol and weight from map */
symbol = map->symbol;
nval = fuzzy_normalize (value, map->weight);
}
- msg_info ("<%s>, found fuzzy hash '%s' with weight: %.2f, in list: %d",
- session->task->message_id, fuzzy_to_string (session->h), flag, nval);
- rspamd_snprintf (buf, sizeof (buf), "%d: %d / %.2f", flag, value, nval);
- insert_result (session->task, symbol, nval, g_list_prepend (NULL,
+ msg_info ("<%s>, found fuzzy hash '%s' with weight: %.2f, in list: %s:%d%s",
+ session->task->message_id, fuzzy_to_string (session->h), nval, symbol,
+ flag, map == NULL ? "(unknown)" : "");
+ if (map != NULL || !session->rule->skip_unknown) {
+ rspamd_snprintf (buf, sizeof (buf), "%d: %d / %.2f", flag, value, nval);
+ insert_result_single (session->task, symbol, nval, g_list_prepend (NULL,
memory_pool_strdup (session->task->task_pool, buf)));
+ }
}
goto ok;
}
@@ -544,7 +572,7 @@ fuzzy_io_callback (gint fd, short what, void *arg)
return;
err:
- msg_err ("got error on IO with server %s:%d, %d, %s", session->server->name, session->server->port, errno, strerror (errno));
+ msg_err ("got error on IO with server %s, %d, %s", session->server->name, errno, strerror (errno));
ok:
remove_normal_event (session->task->s, fuzzy_io_fin, session);
}
@@ -564,8 +592,10 @@ fuzzy_learn_callback (gint fd, short what, void *arg)
struct fuzzy_learn_session *session = arg;
struct fuzzy_cmd cmd;
gchar buf[512];
+ const gchar *cmd_name;
gint r;
+ cmd_name = (session->cmd == FUZZY_WRITE ? "add" : "delete");
if (what == EV_WRITE) {
/* Send command to storage */
cmd.blocksize = session->h->block_size;
@@ -575,7 +605,9 @@ fuzzy_learn_callback (gint fd, short what, void *arg)
cmd.flag = session->flag;
if (write (fd, &cmd, sizeof (struct fuzzy_cmd)) == -1) {
if (*(session->err) == NULL) {
- g_set_error (session->err, g_quark_from_static_string ("fuzzy check"), 404, "write socket error: %s", strerror (errno));
+ g_set_error (session->err,
+ g_quark_from_static_string ("fuzzy check"),
+ errno, "write socket error: %s", strerror (errno));
}
goto err;
}
@@ -587,34 +619,46 @@ fuzzy_learn_callback (gint fd, short what, void *arg)
}
else if (what == EV_READ) {
if (read (fd, buf, sizeof (buf)) == -1) {
- msg_info ("cannot add fuzzy hash for message <%s>", session->task->message_id);
+ msg_info ("cannot %s fuzzy hash for message <%s>, list %s:%d", cmd_name,
+ session->task->message_id, session->rule->symbol, session->flag);
if (*(session->err) == NULL) {
- g_set_error (session->err, g_quark_from_static_string ("fuzzy check"), 404, "read socket error: %s", strerror (errno));
+ g_set_error (session->err,
+ g_quark_from_static_string ("fuzzy check"),
+ errno, "read socket error: %s", strerror (errno));
}
goto err;
}
else if (buf[0] == 'O' && buf[1] == 'K') {
- msg_info ("added fuzzy hash '%s' to list: %d for message <%s>",
- fuzzy_to_string (session->h), session->flag, session->task->message_id);
+ msg_info ("%s fuzzy hash '%s', list: %s:%d for message <%s>", cmd_name,
+ fuzzy_to_string (session->h), session->rule->symbol,
+ session->flag, session->task->message_id);
goto ok;
}
else {
- msg_info ("cannot add fuzzy hash for message <%s>", session->task->message_id);
+ msg_info ("cannot %s fuzzy hash '%s' for message <%s>, list %s:%d", cmd_name,
+ fuzzy_to_string (session->h), session->task->message_id,
+ session->rule->symbol, session->flag);
if (*(session->err) == NULL) {
- g_set_error (session->err, g_quark_from_static_string ("fuzzy check"), 500, "add fuzzy error");
+ g_set_error (session->err,
+ g_quark_from_static_string ("fuzzy check"), EINVAL, "%s fuzzy error", cmd_name);
}
goto ok;
}
}
else {
errno = ETIMEDOUT;
+ if (*(session->err) == NULL) {
+ g_set_error (session->err,
+ g_quark_from_static_string ("fuzzy check"), EINVAL, "%s fuzzy, IO timeout", cmd_name);
+ }
goto err;
}
return;
err:
- msg_err ("got error in IO with server %s:%d, %d, %s", session->server->name, session->server->port, errno, strerror (errno));
+ msg_err ("got error in IO with server %s, %d, %s",
+ session->server->name, errno, strerror (errno));
ok:
if (--(*(session->saved)) == 0) {
session->session->state = STATE_REPLY;
@@ -648,7 +692,7 @@ ok:
}
static inline void
-register_fuzzy_call (struct worker_task *task, fuzzy_hash_t *h)
+register_fuzzy_call (struct worker_task *task, struct fuzzy_rule *rule, fuzzy_hash_t *h)
{
struct fuzzy_client_session *session;
struct storage_server *selected;
@@ -656,11 +700,12 @@ register_fuzzy_call (struct worker_task *task, fuzzy_hash_t *h)
/* Get upstream */
#ifdef HAVE_CLOCK_GETTIME
- selected = (struct storage_server *)get_upstream_by_hash (fuzzy_module_ctx->servers, fuzzy_module_ctx->servers_num,
+ selected = (struct storage_server *)get_upstream_by_hash (rule->servers, rule->servers_num,
sizeof (struct storage_server), task->ts.tv_sec,
- DEFAULT_UPSTREAM_ERROR_TIME, DEFAULT_UPSTREAM_DEAD_TIME, DEFAULT_UPSTREAM_MAXERRORS, h->hash_pipe, sizeof (h->hash_pipe));
+ DEFAULT_UPSTREAM_ERROR_TIME, DEFAULT_UPSTREAM_DEAD_TIME, DEFAULT_UPSTREAM_MAXERRORS,
+ h->hash_pipe, sizeof (h->hash_pipe));
#else
- selected = (struct storage_server *)get_upstream_by_hash (fuzzy_module_ctx->servers, fuzzy_module_ctx->servers_num,
+ selected = (struct storage_server *)get_upstream_by_hash (rule->servers, rule->servers_num,
sizeof (struct storage_server), task->tv.tv_sec,
DEFAULT_UPSTREAM_ERROR_TIME, DEFAULT_UPSTREAM_DEAD_TIME, DEFAULT_UPSTREAM_MAXERRORS, h->hash_pipe, sizeof (h->hash_pipe));
#endif
@@ -678,15 +723,15 @@ register_fuzzy_call (struct worker_task *task, fuzzy_hash_t *h)
session->task = task;
session->fd = sock;
session->server = selected;
+ session->rule = rule;
event_add (&session->ev, &session->tv);
register_async_event (task->s, fuzzy_io_fin, session, g_quark_from_static_string ("fuzzy check"));
}
}
}
-/* This callback is called when we check message via fuzzy hashes storage */
static void
-fuzzy_symbol_callback (struct worker_task *task, void *unused)
+fuzzy_check_rule (struct worker_task *task, struct fuzzy_rule *rule)
{
struct mime_text_part *part;
struct mime_part *mime_part;
@@ -696,25 +741,6 @@ fuzzy_symbol_callback (struct worker_task *task, void *unused)
GList *cur;
fuzzy_hash_t *fake_fuzzy;
-
- /* Check whitelist */
-#ifdef HAVE_INET_PTON
- if (fuzzy_module_ctx->whitelist && !task->from_addr.ipv6 && task->from_addr.d.in4.s_addr != INADDR_NONE) {
- if (radix32tree_find (fuzzy_module_ctx->whitelist, ntohl ((guint32) task->from_addr.d.in4.s_addr)) != RADIX_NO_VALUE) {
- msg_info ("<%s>, address %s is whitelisted, skip fuzzy check",
- task->message_id, inet_ntoa (task->from_addr.d.in4));
- return;
- }
- }
-#else
- if (fuzzy_module_ctx->whitelist && task->from_addr.s_addr != 0) {
- if (radix32tree_find (fuzzy_module_ctx->whitelist, ntohl ((guint32) task->from_addr.s_addr)) != RADIX_NO_VALUE) {
- msg_info ("<%s>, address %s is whitelisted, skip fuzzy check",
- task->message_id, inet_ntoa (task->from_addr));
- return;
- }
- }
-#endif
cur = task->text_parts;
while (cur) {
@@ -747,8 +773,8 @@ fuzzy_symbol_callback (struct worker_task *task, void *unused)
continue;
}
- register_fuzzy_call (task, part->fuzzy);
- register_fuzzy_call (task, part->double_fuzzy);
+ register_fuzzy_call (task, rule, part->fuzzy);
+ register_fuzzy_call (task, rule, part->double_fuzzy);
cur = g_list_next (cur);
}
@@ -763,7 +789,7 @@ fuzzy_symbol_callback (struct worker_task *task, void *unused)
/* Construct fake fuzzy hash */
fake_fuzzy = memory_pool_alloc0 (task->task_pool, sizeof (fuzzy_hash_t));
rspamd_strlcpy (fake_fuzzy->hash_pipe, checksum, sizeof (fake_fuzzy->hash_pipe));
- register_fuzzy_call (task, fake_fuzzy);
+ register_fuzzy_call (task, rule, fake_fuzzy);
g_free (checksum);
}
}
@@ -774,13 +800,14 @@ fuzzy_symbol_callback (struct worker_task *task, void *unused)
cur = task->parts;
while (cur) {
mime_part = cur->data;
- if (mime_part->content->len > 0 && fuzzy_check_content_type (mime_part->type)) {
+ if (mime_part->content->len > 0 && fuzzy_check_content_type (rule, mime_part->type)) {
if (fuzzy_module_ctx->min_bytes <= 0 || mime_part->content->len >= fuzzy_module_ctx->min_bytes) {
- checksum = g_compute_checksum_for_data (G_CHECKSUM_MD5, mime_part->content->data, mime_part->content->len);
+ checksum = g_compute_checksum_for_data (G_CHECKSUM_MD5,
+ mime_part->content->data, mime_part->content->len);
/* Construct fake fuzzy hash */
fake_fuzzy = memory_pool_alloc0 (task->task_pool, sizeof (fuzzy_hash_t));
rspamd_strlcpy (fake_fuzzy->hash_pipe, checksum, sizeof (fake_fuzzy->hash_pipe));
- register_fuzzy_call (task, fake_fuzzy);
+ register_fuzzy_call (task, rule, fake_fuzzy);
g_free (checksum);
}
}
@@ -788,41 +815,64 @@ fuzzy_symbol_callback (struct worker_task *task, void *unused)
}
}
+/* This callback is called when we check message via fuzzy hashes storage */
+static void
+fuzzy_symbol_callback (struct worker_task *task, void *unused)
+{
+ struct fuzzy_rule *rule;
+ GList *cur;
+
+ /* Check whitelist */
+#ifdef HAVE_INET_PTON
+ if (fuzzy_module_ctx->whitelist && !task->from_addr.ipv6 && task->from_addr.d.in4.s_addr != INADDR_NONE) {
+ if (radix32tree_find (fuzzy_module_ctx->whitelist, ntohl ((guint32) task->from_addr.d.in4.s_addr)) != RADIX_NO_VALUE) {
+ msg_info ("<%s>, address %s is whitelisted, skip fuzzy check",
+ task->message_id, inet_ntoa (task->from_addr.d.in4));
+ return;
+ }
+ }
+#else
+ if (fuzzy_module_ctx->whitelist && task->from_addr.s_addr != 0) {
+ if (radix32tree_find (fuzzy_module_ctx->whitelist, ntohl ((guint32) task->from_addr.s_addr)) != RADIX_NO_VALUE) {
+ msg_info ("<%s>, address %s is whitelisted, skip fuzzy check",
+ task->message_id, inet_ntoa (task->from_addr));
+ return;
+ }
+ }
+#endif
+
+ cur = fuzzy_module_ctx->fuzzy_rules;
+ while (cur) {
+ rule = cur->data;
+ fuzzy_check_rule (task, rule);
+ cur = g_list_next (cur);
+ }
+}
+
static inline gboolean
-register_fuzzy_controller_call (struct controller_session *session, struct worker_task *task, fuzzy_hash_t *h,
- gint cmd, gint value, gint flag, gint *saved, GError **err)
+register_fuzzy_controller_call (struct controller_session *session,
+ struct fuzzy_rule *rule, struct worker_task *task, fuzzy_hash_t *h,
+ gint cmd, gint value, gint flag, gint *saved, GError **err)
{
struct fuzzy_learn_session *s;
struct storage_server *selected;
- gint sock, r;
- gchar out_buf[BUFSIZ];
+ gint sock;
/* Get upstream */
#ifdef HAVE_CLOCK_GETTIME
- selected = (struct storage_server *)get_upstream_by_hash (fuzzy_module_ctx->servers, fuzzy_module_ctx->servers_num,
+ selected = (struct storage_server *)get_upstream_by_hash (rule->servers, rule->servers_num,
sizeof (struct storage_server), task->ts.tv_sec,
- DEFAULT_UPSTREAM_ERROR_TIME, DEFAULT_UPSTREAM_DEAD_TIME, DEFAULT_UPSTREAM_MAXERRORS, h->hash_pipe, sizeof (h->hash_pipe));
+ DEFAULT_UPSTREAM_ERROR_TIME, DEFAULT_UPSTREAM_DEAD_TIME, DEFAULT_UPSTREAM_MAXERRORS,
+ h->hash_pipe, sizeof (h->hash_pipe));
#else
- selected = (struct storage_server *)get_upstream_by_hash (fuzzy_module_ctx->servers, fuzzy_module_ctx->servers_num,
+ selected = (struct storage_server *)get_upstream_by_hash (rule->servers, rule->servers_num,
sizeof (struct storage_server), task->tv.tv_sec,
- DEFAULT_UPSTREAM_ERROR_TIME, DEFAULT_UPSTREAM_DEAD_TIME, DEFAULT_UPSTREAM_MAXERRORS, h->hash_pipe, sizeof (h->hash_pipe));
+ DEFAULT_UPSTREAM_ERROR_TIME, DEFAULT_UPSTREAM_DEAD_TIME, DEFAULT_UPSTREAM_MAXERRORS,
+ h->hash_pipe, sizeof (h->hash_pipe));
#endif
if (selected) {
/* Create UDP socket */
if ((sock = make_universal_socket (selected->addr, selected->port, SOCK_DGRAM, TRUE, FALSE, FALSE)) == -1) {
- msg_warn ("cannot connect to %s, %d, %s", selected->name, errno, strerror (errno));
- session->state = STATE_REPLY;
- if (session->restful) {
- r = rspamd_snprintf (out_buf, sizeof (out_buf), "HTTP/1.0 404 No hashes have been written" CRLF CRLF);
- }
- else {
- r = rspamd_snprintf (out_buf, sizeof (out_buf), "no hashes have been written" CRLF "END" CRLF);
- }
- if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) {
- return FALSE;
- }
- free_task (task, FALSE);
- rspamd_dispatcher_restore (session->dispatcher);
return FALSE;
}
else {
@@ -841,27 +891,120 @@ register_fuzzy_controller_call (struct controller_session *session, struct worke
s->saved = saved;
s->fd = sock;
s->err = err;
+ s->rule = rule;
event_add (&s->ev, &s->tv);
(*saved)++;
register_async_event (session->s, fuzzy_learn_fin, s, g_quark_from_static_string ("fuzzy check"));
return TRUE;
}
}
+
return FALSE;
}
-static void
-fuzzy_process_handler (struct controller_session *session, f_str_t * in)
+static gboolean
+fuzzy_process_rule (struct controller_session *session, struct fuzzy_rule *rule,
+ struct worker_task *task, GError **err, gint cmd, gint flag, gint value, gint *saved)
{
- struct worker_task *task;
struct mime_text_part *part;
struct mime_part *mime_part;
struct rspamd_image *image;
- GList *cur;
- GError **err;
- gint r, cmd = 0, value = 0, flag = 0, *saved, *sargs;
- gchar out_buf[BUFSIZ], *checksum;
+ GList *cur;
+ gchar *checksum;
fuzzy_hash_t fake_fuzzy;
+ gboolean processed = FALSE;
+
+ /* Plan new event for writing */
+ cur = task->text_parts;
+
+ while (cur) {
+ part = cur->data;
+ if (part->is_empty || part->fuzzy == NULL || part->fuzzy->hash_pipe[0] == '\0' ||
+ (fuzzy_module_ctx->min_bytes > 0 && part->content->len < fuzzy_module_ctx->min_bytes)) {
+ /* Skip empty parts */
+ cur = g_list_next (cur);
+ continue;
+ }
+ if (! register_fuzzy_controller_call (session, rule, task,
+ part->fuzzy, cmd, value, flag, saved, err)) {
+ return FALSE;
+ }
+ if (! register_fuzzy_controller_call (session, rule, task,
+ part->double_fuzzy, cmd, value, flag, saved, err)) {
+ /* Cannot write hash */
+ return FALSE;
+ }
+ processed = TRUE;
+ cur = g_list_next (cur);
+ }
+
+ /* Process images */
+ cur = task->images;
+ while (cur) {
+ image = cur->data;
+ if (image->data->len > 0) {
+ if (fuzzy_module_ctx->min_height <= 0 || image->height >= fuzzy_module_ctx->min_height) {
+ if (fuzzy_module_ctx->min_width <= 0 || image->width >= fuzzy_module_ctx->min_width) {
+ checksum = g_compute_checksum_for_data (G_CHECKSUM_MD5, image->data->data, image->data->len);
+ /* Construct fake fuzzy hash */
+ fake_fuzzy.block_size = 0;
+ memset (fake_fuzzy.hash_pipe, 0, sizeof (fake_fuzzy.hash_pipe));
+ rspamd_strlcpy (fake_fuzzy.hash_pipe, checksum, sizeof (fake_fuzzy.hash_pipe));
+ if (! register_fuzzy_controller_call (session, rule, task,
+ &fake_fuzzy, cmd, value, flag, saved, err)) {
+ g_free (checksum);
+ return FALSE;
+ }
+
+ msg_info ("save hash of image: [%s] to list: %d", checksum, flag);
+ g_free (checksum);
+ processed = TRUE;
+ }
+ }
+ }
+ cur = g_list_next (cur);
+ }
+ /* Process other parts */
+ cur = task->parts;
+ while (cur) {
+ mime_part = cur->data;
+ if (mime_part->content->len > 0 && fuzzy_check_content_type (rule, mime_part->type)) {
+ if (fuzzy_module_ctx->min_bytes <= 0 || mime_part->content->len >= fuzzy_module_ctx->min_bytes) {
+ checksum = g_compute_checksum_for_data (G_CHECKSUM_MD5,
+ mime_part->content->data, mime_part->content->len);
+ /* Construct fake fuzzy hash */
+ fake_fuzzy.block_size = 0;
+ memset (fake_fuzzy.hash_pipe, 0, sizeof (fake_fuzzy.hash_pipe));
+ rspamd_strlcpy (fake_fuzzy.hash_pipe, checksum, sizeof (fake_fuzzy.hash_pipe));
+ if (! register_fuzzy_controller_call (session, rule, task,
+ &fake_fuzzy, cmd, value, flag, saved, err)) {
+ return FALSE;
+ }
+ msg_info ("save hash of part of type: %s/%s: [%s] to list %d",
+ mime_part->type->type, mime_part->type->subtype,
+ checksum, flag);
+ g_free (checksum);
+ processed = TRUE;
+ }
+ }
+ cur = g_list_next (cur);
+ }
+
+ memory_pool_add_destructor (session->session_pool, (pool_destruct_func)free_task_soft, task);
+
+ return processed;
+}
+
+static gboolean
+fuzzy_process_handler (struct controller_session *session, f_str_t * in)
+{
+ struct fuzzy_rule *rule;
+ gboolean processed = FALSE, res = TRUE;
+ GList *cur;
+ struct worker_task *task;
+ GError **err;
+ gint r, cmd = 0, value = 0, flag = 0, *saved, *sargs;
+ gchar out_buf[BUFSIZ];
/* Extract arguments */
if (session->other_data) {
@@ -870,17 +1013,17 @@ fuzzy_process_handler (struct controller_session *session, f_str_t * in)
value = sargs[1];
flag = sargs[2];
}
-
+
/* Prepare task */
task = construct_task (session->worker);
session->other_data = task;
session->state = STATE_WAIT;
-
+
/* Allocate message from string */
task->msg = memory_pool_alloc (task->task_pool, sizeof (f_str_t));
task->msg->begin = in->begin;
task->msg->len = in->len;
-
+
saved = memory_pool_alloc0 (session->session_pool, sizeof (gint));
err = memory_pool_alloc0 (session->session_pool, sizeof (GError *));
@@ -899,132 +1042,33 @@ fuzzy_process_handler (struct controller_session *session, f_str_t * in)
msg_warn ("write error");
}
rspamd_dispatcher_restore (session->dispatcher);
- return;
+ return FALSE;
}
- else {
- /* Plan new event for writing */
- cur = task->text_parts;
+ cur = fuzzy_module_ctx->fuzzy_rules;
+ while (cur && res) {
+ rule = cur->data;
- while (cur) {
- part = cur->data;
- if (part->is_empty || part->fuzzy == NULL || part->fuzzy->hash_pipe[0] == '\0' ||
- (fuzzy_module_ctx->min_bytes > 0 && part->content->len < fuzzy_module_ctx->min_bytes)) {
- /* Skip empty parts */
- cur = g_list_next (cur);
- continue;
- }
- if (! register_fuzzy_controller_call (session, task, part->fuzzy, cmd, value, flag, saved, err)) {
- /* Cannot write hash */
- session->state = STATE_REPLY;
- if (session->restful) {
- r = rspamd_snprintf (out_buf, sizeof (out_buf), "HTTP/1.0 500 Cannot write fuzzy hash" CRLF CRLF);
- }
- else {
- r = rspamd_snprintf (out_buf, sizeof (out_buf), "cannot write fuzzy hash" CRLF "END" CRLF);
- }
- if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) {
- return;
- }
- rspamd_dispatcher_restore (session->dispatcher);
- free_task (task, FALSE);
- return;
- }
- if (! register_fuzzy_controller_call (session, task, part->double_fuzzy, cmd, value, flag, saved, err)) {
- /* Cannot write hash */
- session->state = STATE_REPLY;
- if (session->restful) {
- r = rspamd_snprintf (out_buf, sizeof (out_buf), "HTTP/1.0 500 Cannot write fuzzy hash" CRLF CRLF);
- }
- else {
- r = rspamd_snprintf (out_buf, sizeof (out_buf), "cannot write fuzzy hash" CRLF "END" CRLF);
- }
- if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) {
- return;
- }
- free_task (task, FALSE);
- rspamd_dispatcher_restore (session->dispatcher);
- return;
- }
+ if (rule->read_only) {
cur = g_list_next (cur);
+ continue;
}
- /* Process images */
- cur = task->images;
- while (cur) {
- image = cur->data;
- if (image->data->len > 0) {
- if (fuzzy_module_ctx->min_height <= 0 || image->height >= fuzzy_module_ctx->min_height) {
- if (fuzzy_module_ctx->min_width <= 0 || image->width >= fuzzy_module_ctx->min_width) {
- checksum = g_compute_checksum_for_data (G_CHECKSUM_MD5, image->data->data, image->data->len);
- /* Construct fake fuzzy hash */
- fake_fuzzy.block_size = 0;
- bzero (fake_fuzzy.hash_pipe, sizeof (fake_fuzzy.hash_pipe));
- rspamd_strlcpy (fake_fuzzy.hash_pipe, checksum, sizeof (fake_fuzzy.hash_pipe));
- if (! register_fuzzy_controller_call (session, task, &fake_fuzzy, cmd, value, flag, saved, err)) {
- /* Cannot write hash */
- session->state = STATE_REPLY;
- if (session->restful) {
- r = rspamd_snprintf (out_buf, sizeof (out_buf), "HTTP/1.0 500 Cannot write fuzzy hash" CRLF CRLF);
- }
- else {
- r = rspamd_snprintf (out_buf, sizeof (out_buf), "cannot write fuzzy hash" CRLF "END" CRLF);
- }
- g_free (checksum);
- free_task (task, FALSE);
- if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) {
- return;
- }
- rspamd_dispatcher_restore (session->dispatcher);
- return;
- }
-
- msg_info ("save hash of image: [%s] to list: %d", checksum, flag);
- g_free (checksum);
- }
- }
- }
+
+ /* Check for flag */
+ if (g_hash_table_lookup (rule->mappings, GINT_TO_POINTER (flag)) == NULL) {
cur = g_list_next (cur);
+ continue;
}
- /* Process other parts */
- cur = task->parts;
- while (cur) {
- mime_part = cur->data;
- if (mime_part->content->len > 0 && fuzzy_check_content_type (mime_part->type)) {
- if (fuzzy_module_ctx->min_bytes <= 0 || mime_part->content->len >= fuzzy_module_ctx->min_bytes) {
- checksum = g_compute_checksum_for_data (G_CHECKSUM_MD5, mime_part->content->data, mime_part->content->len);
- /* Construct fake fuzzy hash */
- fake_fuzzy.block_size = 0;
- bzero (fake_fuzzy.hash_pipe, sizeof (fake_fuzzy.hash_pipe));
- rspamd_strlcpy (fake_fuzzy.hash_pipe, checksum, sizeof (fake_fuzzy.hash_pipe));
- if (! register_fuzzy_controller_call (session, task, &fake_fuzzy, cmd, value, flag, saved, err)) {
- /* Cannot write hash */
- session->state = STATE_REPLY;
- if (session->restful) {
- r = rspamd_snprintf (out_buf, sizeof (out_buf), "HTTP/1.0 500 Cannot write fuzzy hash" CRLF CRLF);
- }
- else {
- r = rspamd_snprintf (out_buf, sizeof (out_buf), "cannot write fuzzy hash" CRLF "END" CRLF);
- }
- g_free (checksum);
- free_task (task, FALSE);
- if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) {
- return;
- }
- rspamd_dispatcher_restore (session->dispatcher);
- return;
- }
- msg_info ("save hash of part of type: %s/%s: [%s] to list %d",
- mime_part->type->type, mime_part->type->subtype,
- checksum, flag);
- g_free (checksum);
- }
- }
- cur = g_list_next (cur);
+
+ res = fuzzy_process_rule (session, rule, task, err, cmd, flag, value, saved);
+
+ if (res) {
+ processed = TRUE;
}
- }
- memory_pool_add_destructor (session->session_pool, (pool_destruct_func)free_task_soft, task);
+ cur = g_list_next (cur);
+ }
- if (*saved == 0) {
+ if (!res) {
session->state = STATE_REPLY;
if (session->restful) {
r = rspamd_snprintf (out_buf, sizeof (out_buf), "HTTP/1.0 404 No hashes have been written" CRLF CRLF);
@@ -1033,13 +1077,28 @@ fuzzy_process_handler (struct controller_session *session, f_str_t * in)
r = rspamd_snprintf (out_buf, sizeof (out_buf), "no hashes have been written" CRLF "END" CRLF);
}
if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) {
- return;
+ return FALSE;
}
- rspamd_dispatcher_restore (session->dispatcher);
+ return FALSE;
+ }
+ else if (!processed) {
+ session->state = STATE_REPLY;
+ if (session->restful) {
+ r = rspamd_snprintf (out_buf, sizeof (out_buf), "HTTP/1.0 404 No fuzzy rules matched" CRLF CRLF);
+ }
+ else {
+ r = rspamd_snprintf (out_buf, sizeof (out_buf), "no fuzzy rules matched" CRLF "END" CRLF);
+ }
+ if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) {
+ return FALSE;
+ }
+ return FALSE;
}
+
+ return TRUE;
}
-static void
+static gboolean
fuzzy_controller_handler (gchar **args, struct controller_session *session, gint cmd)
{
gchar *arg, out_buf[BUFSIZ], *err_str;
@@ -1053,22 +1112,22 @@ fuzzy_controller_handler (gchar **args, struct controller_session *session, gint
msg_info ("empty content length");
r = rspamd_snprintf (out_buf, sizeof (out_buf), "HTTP/1.0 500 Fuzzy command requires Content-Length" CRLF CRLF);
if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) {
- return;
+ return FALSE;
}
session->state = STATE_REPLY;
rspamd_dispatcher_restore (session->dispatcher);
- return;
+ return FALSE;
}
errno = 0;
size = strtoul (arg, &err_str, 10);
if (errno != 0 || (err_str && *err_str != '\0')) {
r = rspamd_snprintf (out_buf, sizeof (out_buf), "HTTP/1.0 500 Learn size is invalid" CRLF CRLF);
if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) {
- return;
+ return FALSE;
}
session->state = STATE_REPLY;
rspamd_dispatcher_restore (session->dispatcher);
- return;
+ return FALSE;
}
arg = g_hash_table_lookup (session->kwargs, "value");
if (arg) {
@@ -1096,20 +1155,20 @@ fuzzy_controller_handler (gchar **args, struct controller_session *session, gint
msg_info ("empty content length");
r = rspamd_snprintf (out_buf, sizeof (out_buf), "fuzzy command requires length as argument" CRLF "END" CRLF);
if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) {
- return;
+ return FALSE;
}
session->state = STATE_REPLY;
- return;
+ return FALSE;
}
errno = 0;
size = strtoul (arg, &err_str, 10);
if (errno != 0 || (err_str && *err_str != '\0')) {
r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn size is invalid" CRLF);
if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) {
- return;
+ return FALSE;
}
session->state = STATE_REPLY;
- return;
+ return FALSE;
}
/* Process value */
arg = args[1];
@@ -1142,23 +1201,18 @@ fuzzy_controller_handler (gchar **args, struct controller_session *session, gint
sargs[1] = value;
sargs[2] = flag;
session->other_data = sargs;
+
+ return TRUE;
}
-static void
+static gboolean
fuzzy_add_handler (gchar **args, struct controller_session *session)
{
- fuzzy_controller_handler (args, session, FUZZY_WRITE);
+ return fuzzy_controller_handler (args, session, FUZZY_WRITE);
}
-static void
+static gboolean
fuzzy_delete_handler (gchar **args, struct controller_session *session)
{
- fuzzy_controller_handler (args, session, FUZZY_DEL);
-}
-
-static gint
-fuzzy_mime_filter (struct worker_task *task)
-{
- /* XXX: remove this */
- return 0;
+ return fuzzy_controller_handler (args, session, FUZZY_DEL);
}
diff --git a/src/plugins/lua/emails.lua b/src/plugins/lua/emails.lua
index 8b4ba5970..162809541 100644
--- a/src/plugins/lua/emails.lua
+++ b/src/plugins/lua/emails.lua
@@ -5,40 +5,16 @@
-- symbol = sym2, dnsbl = bl.somehost.com, domain_only = no
local rules = {}
-function split(str, delim, maxNb)
- -- Eliminate bad cases...
- if string.find(str, delim) == nil then
- return { str }
- end
- if maxNb == nil or maxNb < 1 then
- maxNb = 0 -- No limit
- end
- local result = {}
- local pat = "(.-)" .. delim .. "()"
- local nb = 0
- local lastPos
- for part, pos in string.gmatch(str, pat) do
- nb = nb + 1
- result[nb] = part
- lastPos = pos
- if nb == maxNb then break end
- end
- -- Handle the last field
- if nb ~= maxNb then
- result[nb + 1] = string.sub(str, lastPos)
- end
- return result
-end
-
-function emails_dns_cb(task, to_resolve, results, err, symbol)
- if results then
- rspamd_logger.info(string.format('<%s> email: [%s] resolved for symbol: %s', task:get_message():get_message_id(), to_resolve, symbol))
- task:insert_result(symbol, 1)
- end
-end
-
-- Check rule for a single email
-function check_email_rule(task, rule, addr)
+local function check_email_rule(task, rule, addr)
+ local function emails_dns_cb(resolver, to_resolve, results, err)
+ task:inc_dns_req()
+ if results then
+ rspamd_logger.info(string.format('<%s> email: [%s] resolved for symbol: %s',
+ task:get_message():get_message_id(), to_resolve, rule['symbol']))
+ task:insert_result(rule['symbol'], 1)
+ end
+ end
if rule['dnsbl'] then
local to_resolve = ''
if rule['domain_only'] then
@@ -46,26 +22,29 @@ function check_email_rule(task, rule, addr)
else
to_resolve = string.format('%s.%s.%s', addr:get_user(), addr:get_host(), rule['dnsbl'])
end
- task:resolve_dns_a(to_resolve, 'emails_dns_cb', rule['symbol'])
+ task:get_resolver():resolve_a(task:get_session(), task:get_mempool(),
+ to_resolve, emails_dns_cb)
elseif rule['map'] then
if rule['domain_only'] then
local key = addr:get_host()
if rule['map']:get_key(key) then
task:insert_result(rule['symbol'], 1)
- rspamd_logger.info(string.format('<%s> email: \'%s\' is found in list: %s', task:get_message():get_message_id(), key, rule['symbol']))
+ rspamd_logger.info(string.format('<%s> email: \'%s\' is found in list: %s',
+ task:get_message():get_message_id(), key, rule['symbol']))
end
else
local key = string.format('%s@%s', addr:get_user(), addr:get_host())
if rule['map']:get_key(key) then
task:insert_result(rule['symbol'], 1)
- rspamd_logger.info(string.format('<%s> email: \'%s\' is found in list: %s', task:get_message():get_message_id(), key, rule['symbol']))
+ rspamd_logger.info(string.format('<%s> email: \'%s\' is found in list: %s',
+ task:get_message():get_message_id(), key, rule['symbol']))
end
end
end
end
-- Check email
-function check_emails(task)
+local function check_emails(task)
local emails = task:get_emails()
local checked = {}
if emails then
@@ -81,40 +60,6 @@ function check_emails(task)
end
end
--- Add rule to ruleset
-local function add_emails_rule(key, obj)
- local newrule = {
- name = nil,
- dnsbl = nil,
- map = nil,
- domain_only = false,
- symbol = key
- }
- for name,value in pairs(obj) do
- if name == 'dnsbl' then
- newrule['dnsbl'] = value
- newrule['name'] = value
- elseif name == 'map' then
- newrule['name'] = value
- newrule['map'] = rspamd_config:add_hash_map (newrule['name'])
- elseif name == 'symbol' then
- newrule['symbol'] = value
- elseif name == 'domain_only' then
- newrule['domain_only'] = value
- else
- rspamd_logger.err('invalid rule option: '.. name)
- return nil
- end
-
- end
- if not newrule['symbol'] or (not newrule['map'] and not newrule['dnsbl']) then
- rspamd_logger.err('incomplete rule')
- return nil
- end
- table.insert(rules, newrule)
- return newrule
-end
-
-- Registration
if type(rspamd_config.get_api_version) ~= 'nil' then
@@ -125,19 +70,23 @@ if type(rspamd_config.get_api_version) ~= 'nil' then
end
end
-local opts = rspamd_config:get_all_opt('emails')
-if opts then
- for k,m in pairs(opts) do
- if type(m) ~= 'table' then
- rspamd_logger.err('parameter ' .. k .. ' is invalid, must be an object')
- else
- local rule = add_emails_rule(k, m)
- if not rule then
- rspamd_logger.err('cannot add rule: "'..k..'"')
+local opts = rspamd_config:get_all_opt('emails', 'rule')
+if opts and type(opts) == 'table' then
+ for k,v in pairs(opts) do
+ if k == 'rule' and type(v) == 'table' then
+ local rule = v
+ if not rule['symbol'] then
+ rule['symbol'] = k
+ end
+ if rule['map'] then
+ rule['name'] = rule['map']
+ rule['map'] = rspamd_config:add_hash_map (rule['name'])
+ end
+ if not rule['symbol'] or (not rule['map'] and not rule['dnsbl']) then
+ rspamd_logger.err('incomplete rule')
else
- if type(rspamd_config.get_api_version) ~= 'nil' then
- rspamd_config:register_virtual_symbol(rule['symbol'], 1.0)
- end
+ table.insert(rules, rule)
+ rspamd_config:register_virtual_symbol(rule['symbol'], 1.0)
end
end
end
@@ -146,8 +95,8 @@ end
if table.maxn(rules) > 0 then
-- add fake symbol to check all maps inside a single callback
if type(rspamd_config.get_api_version) ~= 'nil' then
- rspamd_config:register_callback_symbol('EMAILS', 1.0, 'check_emails')
+ rspamd_config:register_callback_symbol('EMAILS', 1.0, check_emails)
else
- rspamd_config:register_symbol('EMAILS', 1.0, 'check_emails')
+ rspamd_config:register_symbol('EMAILS', 1.0, check_emails)
end
end
diff --git a/src/plugins/lua/forged_recipients.lua b/src/plugins/lua/forged_recipients.lua
index a46776f91..9ad47a259 100644
--- a/src/plugins/lua/forged_recipients.lua
+++ b/src/plugins/lua/forged_recipients.lua
@@ -37,7 +37,7 @@ function check_forged_headers(task)
end
-- Check sender
local smtp_from = task:get_from()
- if smtp_form then
+ if smtp_from then
local mime_from = task:get_from_headers()
if not mime_from or not (string.lower(mime_from[1]['addr']) == string.lower(smtp_from[1]['addr'])) then
task:insert_result(symbol_sender, 1)
diff --git a/src/plugins/lua/multimap.lua b/src/plugins/lua/multimap.lua
index 229d594cf..1711c212e 100644
--- a/src/plugins/lua/multimap.lua
+++ b/src/plugins/lua/multimap.lua
@@ -66,7 +66,7 @@ local function check_multimap(task)
end
elseif rule['type'] == 'dnsbl' then
local ip = task:get_from_ip()
- if ip and ip ~= "0.0.0.0" then
+ if ip and ip:to_string() ~= "0.0.0.0" then
if ip:get_version() == 6 and rule['ipv6'] then
task:get_resolver():resolve_a(task:get_session(), task:get_mempool(),
ip_to_rbl(ip, rule['map']), multimap_rbl_cb, rule['map'])
diff --git a/src/plugins/lua/once_received.lua b/src/plugins/lua/once_received.lua
index 403646489..e2d4496ac 100644
--- a/src/plugins/lua/once_received.lua
+++ b/src/plugins/lua/once_received.lua
@@ -6,33 +6,34 @@ local symbol_strict = nil
local bad_hosts = {}
local good_hosts = {}
-function recv_dns_cb(task, to_resolve, results, err)
- if not results then
- task:insert_result(symbol_strict, 1)
- else
- rspamd_logger.info(string.format('SMTP resolver failed to resolve: %s is %s', to_resolve, results[1]))
- local i = true
- for _,h in ipairs(bad_hosts) do
- if string.find(results[1], h) then
- -- Check for good hostname
- if good_hosts then
- for _,gh in ipairs(good_hosts) do
- if string.find(results[1], gh) then
- i = false
- break
+local function check_quantity_received (task)
+ local function recv_dns_cb(resolver, to_resolve, results, err)
+ task:inc_dns_req()
+ if not results then
+ task:insert_result(symbol_strict, 1)
+ else
+ rspamd_logger.info(string.format('SMTP resolver failed to resolve: %s is %s', to_resolve, results[1]))
+ local i = true
+ for _,h in ipairs(bad_hosts) do
+ if string.find(results[1], h) then
+ -- Check for good hostname
+ if good_hosts then
+ for _,gh in ipairs(good_hosts) do
+ if string.find(results[1], gh) then
+ i = false
+ break
+ end
end
end
- end
- if i then
- task:insert_result(symbol_strict, 1, h)
- return
+ if i then
+ task:insert_result(symbol_strict, 1, h)
+ return
+ end
end
end
end
end
-end
-function check_quantity_received (task)
local recvh = task:get_received_headers()
if table.maxn(recvh) <= 1 then
task:insert_result(symbol, 1)
@@ -43,10 +44,13 @@ function check_quantity_received (task)
return
end
-- Unresolved host
- if not r['real_hostname'] or string.lower(r['real_hostname']) == 'unknown' or string.match(r['real_hostname'], '^%d+%.%d+%.%d+%.%d+$') then
+ if not r['real_hostname'] or string.lower(r['real_hostname']) == 'unknown' or
+ string.match(r['real_hostname'], '^%d+%.%d+%.%d+%.%d+$') then
+
if r['real_ip'] then
-- Try to resolve it again
- task:resolve_dns_ptr(r['real_ip'], 'recv_dns_cb')
+ task:get_resolver():resolve_ptr(task:get_session(), task:get_mempool(),
+ tostring(r['real_ip']), recv_dns_cb)
else
task:insert_result(symbol_strict, 1)
end
@@ -91,7 +95,7 @@ end
local opts = rspamd_config:get_all_opt('once_received')
if opts then
if opts['symbol'] then
- symbol = opts['symbol']
+ local symbol = opts['symbol']
for n,v in pairs(opts) do
if n == 'symbol_strict' then
@@ -115,6 +119,6 @@ if opts then
end
-- Register symbol's callback
- rspamd_config:register_symbol(symbol, 1.0, 'check_quantity_received')
+ rspamd_config:register_symbol(symbol, 1.0, check_quantity_received)
end
end
diff --git a/src/plugins/lua/rbl.lua b/src/plugins/lua/rbl.lua
index cb827e854..e7e720908 100644
--- a/src/plugins/lua/rbl.lua
+++ b/src/plugins/lua/rbl.lua
@@ -78,7 +78,7 @@ local function rbl_cb (task)
end
local rip = task:get_from_ip()
- if(rip:to_string() ~= "0.0.0.0") then
+ if rip and (rip:to_string() ~= '0.0.0.0') then
for k,rbl in pairs(rbls) do
if (rip:get_version() == 6 and rbl['ipv6'] and rbl['from']) or
(rip:get_version() == 4 and rbl['ipv4'] and rbl['from']) then
@@ -89,7 +89,7 @@ local function rbl_cb (task)
end
local recvh = task:get_received_headers()
for _,rh in ipairs(recvh) do
- if rh['real_ip'] then
+ if rh['real_ip'] and rh['real_ip']:to_string() ~= '0.0.0.0' then
for k,rbl in pairs(rbls) do
if (rh['real_ip']:get_version() == 6 and rbl['ipv6'] and rbl['received']) or
(rh['real_ip']:get_version() == 4 and rbl['ipv4'] and rbl['received']) then
diff --git a/src/plugins/lua/whitelist.lua b/src/plugins/lua/whitelist.lua
index 0f4c41f85..7a196ec89 100644
--- a/src/plugins/lua/whitelist.lua
+++ b/src/plugins/lua/whitelist.lua
@@ -22,10 +22,14 @@ function check_whitelist (task)
-- check client's from domain
local from = task:get_from()
if from then
- local _,_,domain = string.find(from, '@(.+)>?$')
- local key = h:get_key(domain)
- if key then
- task:insert_result(symbol_from, 1)
+ local from_addr = from[1]['addr']
+
+ if from_addr then
+ local _,_,domain = string.find(from_addr, '@(.+)>?$')
+ local key = h:get_key(domain)
+ if key then
+ task:insert_result(symbol_from, 1)
+ end
end
end
end
diff --git a/src/printf.c b/src/printf.c
index b03e1475f..d72ec95c8 100644
--- a/src/printf.c
+++ b/src/printf.c
@@ -169,128 +169,136 @@ rspamd_sprintf_num (gchar *buf, gchar *last, guint64 ui64, gchar zero,
return ((gchar *)memcpy (buf, p, len)) + len;
}
-gint
-rspamd_fprintf (FILE *f, const gchar *fmt, ...)
+struct rspamd_printf_char_buf {
+ char *begin;
+ char *pos;
+ glong remain;
+};
+
+static glong
+rspamd_printf_append_char (const gchar *buf, glong buflen, gpointer ud)
{
- va_list args;
- gchar buf[BUFSIZ];
- gint r;
+ struct rspamd_printf_char_buf *dst = (struct rspamd_printf_char_buf *)ud;
+ glong wr;
- va_start (args, fmt);
- rspamd_vsnprintf (buf, sizeof (buf), fmt, args);
- va_end (args);
+ if (dst->remain <= 0) {
+ return dst->remain;
+ }
- r = fprintf (f, "%s", buf);
+ wr = MIN (dst->remain, buflen);
+ memcpy (dst->pos, buf, wr);
+ dst->remain -= wr;
+ dst->pos += wr;
- return r;
+ return wr;
}
-gint
-rspamd_log_fprintf (FILE *f, const gchar *fmt, ...)
+static glong
+rspamd_printf_append_file (const gchar *buf, glong buflen, gpointer ud)
+{
+ FILE *dst = (FILE *)ud;
+
+ return fwrite (buf, 1, buflen, dst);
+}
+
+static glong
+rspamd_printf_append_gstring (const gchar *buf, glong buflen, gpointer ud)
+{
+ GString *dst = (GString *)ud;
+
+ g_string_append_len (dst, buf, buflen);
+
+ return buflen;
+}
+
+glong
+rspamd_fprintf (FILE *f, const gchar *fmt, ...)
{
va_list args;
- gchar buf[BUFSIZ];
- gint r;
+ glong r;
va_start (args, fmt);
- rspamd_vsnprintf (buf, sizeof (buf), fmt, args);
+ r = rspamd_vprintf_common (rspamd_printf_append_file, f, fmt, args);
va_end (args);
- r = fprintf (f, "%s\n", buf);
- fflush (f);
-
- return r;
+ return r;
}
-gint
-rspamd_sprintf (gchar *buf, const gchar *fmt, ...)
+glong
+rspamd_log_fprintf (FILE *f, const gchar *fmt, ...)
{
- gchar *p;
va_list args;
+ glong r;
va_start (args, fmt);
- p = rspamd_vsnprintf (buf, /* STUB */ 65536, fmt, args);
+ r = rspamd_vprintf_common (rspamd_printf_append_file, f, fmt, args);
va_end (args);
- return p - buf;
+ fflush (f);
+
+ return r;
}
-gint
+glong
rspamd_snprintf (gchar *buf, glong max, const gchar *fmt, ...)
{
- gchar *p;
- va_list args;
+ gchar *r;
+ va_list args;
va_start (args, fmt);
- p = rspamd_vsnprintf (buf, max - 1, fmt, args);
+ r = rspamd_vsnprintf (buf, max, fmt, args);
va_end (args);
- *p = '\0';
- return p - buf;
+ return (r - buf);
}
gchar *
-rspamd_escape_string (gchar *dst, const gchar *src, glong len)
+rspamd_vsnprintf (gchar *buf, glong max, const gchar *fmt, va_list args)
{
- gchar *buf = dst, *last = dst + len;
- guint8 c;
- const gchar *p = src;
- gunichar uc;
+ struct rspamd_printf_char_buf dst;
- if (len <= 0) {
- return dst;
- }
+ dst.begin = buf;
+ dst.pos = dst.begin;
+ dst.remain = max - 1;
+ (void)rspamd_vprintf_common (rspamd_printf_append_char, &dst, fmt, args);
+ *dst.pos = '\0';
- while (*p && buf < last) {
- /* Detect utf8 */
- uc = g_utf8_get_char_validated (p, last - buf);
- if (uc > 0) {
- c = g_unichar_to_utf8 (uc, buf);
- buf += c;
- p += c;
- }
- else {
- c = *p ++;
- if (G_UNLIKELY ((c & 0x80))) {
- c &= 0x7F;
- if (last - buf >= 3) {
- *buf++ = 'M';
- *buf++ = '-';
- }
- }
- if (G_UNLIKELY ( g_ascii_iscntrl (c))) {
- if (c == '\n') {
- *buf++ = ' ';
- }
- else if (c == '\t') {
- *buf++ = '\t';
- }
- else {
- *buf++ = '^';
- if (buf != last) {
- *buf++ = c ^ 0100;
- }
- }
- }
- else {
- *buf++ = c;
- }
- }
- }
+ return dst.pos;
+}
- *buf = '\0';
+glong
+rspamd_printf_gstring (GString *s, const gchar *fmt, ...)
+{
+ va_list args;
+ glong r;
- return buf;
+ va_start (args, fmt);
+ r = rspamd_vprintf_common (rspamd_printf_append_gstring, s, fmt, args);
+ va_end (args);
+
+ return r;
}
-gchar *
-rspamd_vsnprintf (gchar *buf, glong max, const gchar *fmt, va_list args)
+#define RSPAMD_PRINTF_APPEND(buf, len) \
+ do { \
+ wr = func ((buf), (len), apd); \
+ if (wr <= 0) { \
+ goto oob; \
+ } \
+ written += wr; \
+ fmt ++; \
+ buf_start = fmt; \
+ } while(0)
+
+glong
+rspamd_vprintf_common (rspamd_printf_append_func func, gpointer apd, const gchar *fmt, va_list args)
{
- gchar *p, zero, *last;
+ gchar zero, numbuf[G_ASCII_DTOSTR_BUF_SIZE], *p, *last, c;
+ const gchar *buf_start = fmt;
gint d;
long double f, scale;
- size_t len, slen;
+ glong written = 0, wr, slen;
gint64 i64;
guint64 ui64;
guint width, sign, hex, humanize, bytes, frac_width, i;
@@ -298,13 +306,7 @@ rspamd_vsnprintf (gchar *buf, glong max, const gchar *fmt, va_list args)
GString *gs;
gboolean bv;
- if (max <= 0) {
- return buf;
- }
-
- last = buf + max;
-
- while (*fmt && buf < last) {
+ while (*fmt) {
/*
* "buf < last" means that we could copy at least one character:
@@ -313,6 +315,15 @@ rspamd_vsnprintf (gchar *buf, glong max, const gchar *fmt, va_list args)
if (*fmt == '%') {
+ /* Append what we have in buf */
+ if (fmt > buf_start) {
+ wr = func (buf_start, fmt - buf_start, apd);
+ if (wr <= 0) {
+ goto oob;
+ }
+ written += wr;
+ }
+
i64 = 0;
ui64 = 0;
@@ -323,7 +334,7 @@ rspamd_vsnprintf (gchar *buf, glong max, const gchar *fmt, va_list args)
bytes = 0;
humanize = 0;
frac_width = 0;
- slen = (size_t) -1;
+ slen = -1;
while (*fmt >= '0' && *fmt <= '9') {
width = width * 10 + *fmt++ - '0';
@@ -376,10 +387,10 @@ rspamd_vsnprintf (gchar *buf, glong max, const gchar *fmt, va_list args)
case '*':
d = (gint)va_arg (args, gint);
if (G_UNLIKELY (d < 0)) {
- msg_err ("crititcal error: size is less than 0");
- g_assert (0);
+ msg_err ("critical error: size is less than 0");
+ return 0;
}
- slen = (size_t)d;
+ slen = (glong)d;
fmt++;
continue;
@@ -395,61 +406,28 @@ rspamd_vsnprintf (gchar *buf, glong max, const gchar *fmt, va_list args)
case 'V':
v = va_arg (args, f_str_t *);
-
- len = v->len;
- len = (buf + len < last) ? len : (size_t) (last - buf);
-
- buf = ((gchar *)memcpy (buf, v->begin, len)) + len;
- fmt++;
+ RSPAMD_PRINTF_APPEND (v->begin, v->len);
continue;
case 'v':
gs = va_arg (args, GString *);
- len = gs->len;
- len = (buf + len < last) ? len : (size_t) (last - buf);
-
- buf = ((gchar *)memcpy (buf, gs->str, len)) + len;
- fmt++;
- break;
-
- case 's':
- p = va_arg(args, gchar *);
- if (p == NULL) {
- p = "(NULL)";
- }
-
- if (slen == (size_t) -1) {
- while (*p && buf < last) {
- *buf++ = *p++;
- }
-
- } else {
- len = (buf + slen < last) ? slen : (size_t) (last - buf);
-
- buf = ((gchar *)memcpy (buf, p, len)) + len;
- }
-
- fmt++;
+ RSPAMD_PRINTF_APPEND (gs->str, gs->len);
continue;
- case 'S':
- p = va_arg(args, gchar *);
+ case 's':
+ p = va_arg (args, gchar *);
if (p == NULL) {
p = "(NULL)";
}
- if (slen == (size_t) -1) {
- buf = rspamd_escape_string (buf, p, last - buf);
-
- } else {
- len = (buf + slen < last) ? slen : (size_t) (last - buf);
-
- buf = rspamd_escape_string (buf, p, len);
+ if (slen == -1) {
+ /* NULL terminated string */
+ slen = strlen (p);
}
- fmt++;
+ RSPAMD_PRINTF_APPEND (p, slen);
continue;
@@ -510,57 +488,28 @@ rspamd_vsnprintf (gchar *buf, glong max, const gchar *fmt, va_list args)
case 'f':
- f = (double) va_arg (args, double);
- if (f < 0) {
- *buf++ = '-';
- f = -f;
+ case 'F':
+ if (*fmt == 'f') {
+ f = (long double) va_arg (args, double);
}
-
- ui64 = (gint64) f;
-
- buf = rspamd_sprintf_num (buf, last, ui64, zero, 0, width);
-
- if (frac_width) {
-
- if (buf < last) {
- *buf++ = '.';
- }
-
- scale = 1.0;
-
- for (i = 0; i < frac_width; i++) {
- scale *= 10.0;
- }
-
- /*
- * (gint64) cast is required for msvc6:
- * it can not convert guint64 to double
- */
- ui64 = (guint64) ((f - (gint64) ui64) * scale);
-
- buf = rspamd_sprintf_num (buf, last, ui64, '0', 0, frac_width);
+ else {
+ f = (long double) va_arg (args, long double);
}
-
- fmt++;
-
- continue;
-
- case 'F':
- f = (long double) va_arg (args, long double);
-
+ p = numbuf;
+ last = p + sizeof (numbuf);
if (f < 0) {
- *buf++ = '-';
+ *p++ = '-';
f = -f;
}
ui64 = (gint64) f;
- buf = rspamd_sprintf_num (buf, last, ui64, zero, 0, width);
+ p = rspamd_sprintf_num (p, last, ui64, zero, 0, width);
if (frac_width) {
- if (buf < last) {
- *buf++ = '.';
+ if (p < last) {
+ *p++ = '.';
}
scale = 1.0;
@@ -575,50 +524,32 @@ rspamd_vsnprintf (gchar *buf, glong max, const gchar *fmt, va_list args)
*/
ui64 = (guint64) ((f - (gint64) ui64) * scale);
- buf = rspamd_sprintf_num (buf, last, ui64, '0', 0, frac_width);
+ p = rspamd_sprintf_num (p, last, ui64, '0', 0, frac_width);
}
- fmt++;
+ slen = p - numbuf;
+ RSPAMD_PRINTF_APPEND (numbuf, slen);
continue;
case 'g':
- f = (long double) va_arg (args, double);
-
- if (f < 0) {
- *buf++ = '-';
- f = -f;
- }
- g_ascii_formatd (buf, last - buf, "%g", (double)f);
- buf += strlen (buf);
- fmt++;
-
- continue;
-
- case 'b':
- bv = (gboolean) va_arg (args, double);
- if (bv) {
- len = MIN (last - buf, 4);
- memcpy (buf, "true", len);
+ case 'G':
+ if (*fmt == 'g') {
+ f = (long double) va_arg (args, double);
}
else {
- len = MIN (last - buf, 5);
- memcpy (buf, "false", len);
+ f = (long double) va_arg (args, long double);
}
- fmt++;
- continue;
+ g_ascii_formatd (numbuf, sizeof (numbuf), "%g", (double)f);
+ slen = strlen (numbuf);
+ RSPAMD_PRINTF_APPEND (numbuf, slen);
- case 'G':
- f = (long double) va_arg (args, long double);
+ continue;
- if (f < 0) {
- *buf++ = '-';
- f = -f;
- }
- g_ascii_formatd (buf, last - buf, "%g", (double)f);
- buf += strlen (buf);
- fmt++;
+ case 'b':
+ bv = (gboolean) va_arg (args, double);
+ RSPAMD_PRINTF_APPEND (bv ? "true" : "false", bv ? 4 : 5);
continue;
@@ -631,39 +562,43 @@ rspamd_vsnprintf (gchar *buf, glong max, const gchar *fmt, va_list args)
break;
case 'c':
- d = va_arg (args, gint);
- *buf++ = (gchar) (d & 0xff);
- fmt++;
+ c = va_arg (args, gint);
+ c &= 0xff;
+ RSPAMD_PRINTF_APPEND (&c, 1);
continue;
case 'Z':
- *buf++ = '\0';
- fmt++;
+ c = '\0';
+ RSPAMD_PRINTF_APPEND (&c, 1);
continue;
case 'N':
- *buf++ = LF;
- fmt++;
+ c = LF;
+ RSPAMD_PRINTF_APPEND (&c, 1);
continue;
case '%':
- *buf++ = '%';
- fmt++;
+ c = '%';
+ RSPAMD_PRINTF_APPEND (&c, 1);
continue;
default:
- *buf++ = *fmt++;
+ c = *fmt;
+ RSPAMD_PRINTF_APPEND (&c, 1);
continue;
}
+ /* Print number */
+ p = numbuf;
+ last = p + sizeof (numbuf);
if (sign) {
if (i64 < 0) {
- *buf++ = '-';
+ *p++ = '-';
ui64 = (guint64) -i64;
} else {
@@ -672,19 +607,29 @@ rspamd_vsnprintf (gchar *buf, glong max, const gchar *fmt, va_list args)
}
if (!humanize) {
- buf = rspamd_sprintf_num (buf, last, ui64, zero, hex, width);
+ p = rspamd_sprintf_num (p, last, ui64, zero, hex, width);
}
else {
- buf = rspamd_humanize_number (buf, last, ui64, bytes);
+ p = rspamd_humanize_number (p, last, ui64, bytes);
}
+ slen = p - numbuf;
+ RSPAMD_PRINTF_APPEND (numbuf, slen);
+ } else {
fmt++;
+ }
+ }
- } else {
- *buf++ = *fmt++;
+ /* Finish buffer */
+ if (fmt > buf_start) {
+ wr = func (buf_start, fmt - buf_start, apd);
+ if (wr <= 0) {
+ goto oob;
}
+ written += wr;
}
- return buf;
+oob:
+ return written;
}
diff --git a/src/printf.h b/src/printf.h
index 9d3921c3a..a4e03791d 100644
--- a/src/printf.h
+++ b/src/printf.h
@@ -47,7 +47,6 @@
* %V f_str_t *
* %v GString *
* %s null-terminated string
- * %S ascii null-terminated string
* %*s length and string
* %Z '\0'
* %N '\n'
@@ -55,20 +54,22 @@
* %% %
*
*/
-gint rspamd_sprintf (gchar *buf, const gchar *fmt, ...);
-gint rspamd_fprintf (FILE *f, const gchar *fmt, ...);
-gint rspamd_log_fprintf (FILE *f, const gchar *fmt, ...);
-gint rspamd_snprintf (gchar *buf, glong max, const gchar *fmt, ...);
-gchar *rspamd_vsnprintf (gchar *buf, glong max, const gchar *fmt, va_list args);
-/*
- * Escape rspamd string to write it to log file or other 7 bit prefferable places
- *
- * @param dst destination string
- * @param src source string
- * @param len length of destination buffer
- * @return pointer to end of buffer
+/**
+ * Callback used for common printf operations
+ * @param buf buffer to append
+ * @param buflen lenght of the buffer
+ * @param ud opaque pointer
+ * @return number of characters written
*/
-gchar * rspamd_escape_string (gchar *dst, const gchar *src, glong len);
+typedef glong (*rspamd_printf_append_func)(const gchar *buf, glong buflen, gpointer ud);
+
+glong rspamd_fprintf (FILE *f, const gchar *fmt, ...);
+glong rspamd_log_fprintf (FILE *f, const gchar *fmt, ...);
+glong rspamd_snprintf (gchar *buf, glong max, const gchar *fmt, ...);
+gchar *rspamd_vsnprintf (gchar *buf, glong max, const gchar *fmt, va_list args);
+glong rspamd_printf_gstring (GString *s, const gchar *fmt, ...);
+
+glong rspamd_vprintf_common (rspamd_printf_append_func func, gpointer apd, const gchar *fmt, va_list args);
#endif /* PRINTF_H_ */
diff --git a/src/spf.c b/src/spf.c
index 755156d4e..e394005b8 100644
--- a/src/spf.c
+++ b/src/spf.c
@@ -181,7 +181,7 @@ spf_addr_find (GList *addrs, gpointer to_find)
addr = cur->data;
if (addr->is_list) {
if ((res = spf_addr_find (addr->data.list, to_find)) != NULL) {
- return res;
+ return cur;
}
}
else {
diff --git a/src/statfile.c b/src/statfile.c
index 9930e0fe0..9df60fd56 100644
--- a/src/statfile.c
+++ b/src/statfile.c
@@ -235,7 +235,7 @@ statfile_pool_reindex (statfile_pool_t * pool, gchar *filename, size_t old_size,
}
pos = map + (sizeof (struct stat_file) - sizeof (struct stat_file_block));
- while (pos - map < (gint)old_size) {
+ while (old_size - (pos - map) >= sizeof (struct stat_file_block)) {
block = (struct stat_file_block *)pos;
if (block->hash1 != 0 && block->value != 0) {
statfile_pool_set_block_common (pool, new, block->hash1, block->hash2, 0, block->value, FALSE);
@@ -307,11 +307,15 @@ statfile_pool_open (statfile_pool_t * pool, gchar *filename, size_t size, gboole
}
memory_pool_lock_mutex (pool->lock);
- if (!forced && abs (st.st_size - size) > (gint)sizeof (struct stat_file)) {
+ if (!forced && labs (size - st.st_size) > (long)sizeof (struct stat_file) * 2
+ && size > sizeof (struct stat_file)) {
memory_pool_unlock_mutex (pool->lock);
- msg_warn ("need to reindex statfile old size: %Hz, new size: %Hz", st.st_size, size);
+ msg_warn ("need to reindex statfile old size: %Hz, new size: %Hz", (size_t)st.st_size, size);
return statfile_pool_reindex (pool, filename, st.st_size, size);
}
+ else if (size < sizeof (struct stat_file)) {
+ msg_err ("requested to shrink statfile to %Hz but it is too small", size);
+ }
new_file = &pool->files[pool->opened++];
bzero (new_file, sizeof (stat_file_t));
diff --git a/src/symbols_cache.c b/src/symbols_cache.c
index c60554f8f..d6c17390d 100644
--- a/src/symbols_cache.c
+++ b/src/symbols_cache.c
@@ -30,7 +30,7 @@
#include "view.h"
#include "cfg_file.h"
-#define WEIGHT_MULT 2.0
+#define WEIGHT_MULT 4.0
#define FREQUENCY_MULT 10.0
#define TIME_MULT -1.0
@@ -58,6 +58,7 @@ cache_logic_cmp (const void *p1, const void *p2)
{
const struct cache_item *i1 = p1, *i2 = p2;
double w1, w2;
+ double weight1, weight2;
double f1 = 0, f2 = 0;
if (i1->priority == 0 && i2->priority == 0) {
@@ -65,8 +66,10 @@ cache_logic_cmp (const void *p1, const void *p2)
f1 = ((double)i1->s->frequency * nsymbols) / (double)total_frequency;
f2 = ((double)i2->s->frequency * nsymbols) / (double)total_frequency;
}
- w1 = abs (i1->s->weight) * WEIGHT_MULT + f1 * FREQUENCY_MULT + i1->s->avg_time * TIME_MULT;
- w2 = abs (i2->s->weight) * WEIGHT_MULT + f2 * FREQUENCY_MULT + i2->s->avg_time * TIME_MULT;
+ weight1 = i1->metric_weight == 0 ? i1->s->weight : i1->metric_weight;
+ weight2 = i2->metric_weight == 0 ? i2->s->weight : i2->metric_weight;
+ w1 = abs (weight1) * WEIGHT_MULT + f1 * FREQUENCY_MULT + i1->s->avg_time * TIME_MULT;
+ w2 = abs (weight2) * WEIGHT_MULT + f2 * FREQUENCY_MULT + i2->s->avg_time * TIME_MULT;
}
else {
/* Strict sorting */
@@ -704,6 +707,34 @@ check_debug_symbol (struct config_file *cfg, const gchar *symbol)
return FALSE;
}
+static void
+rspamd_symbols_cache_metric_cb (gpointer k, gpointer v, gpointer ud)
+{
+ struct symbols_cache *cache = (struct symbols_cache *)ud;
+ GList *cur;
+ const gchar *sym = k;
+ gdouble weight = *(gdouble *)v;
+ struct cache_item *item;
+
+ cur = cache->negative_items;
+ while (cur) {
+ item = cur->data;
+ if (strcmp (item->s->symbol, sym) == 0) {
+ item->metric_weight = weight;
+ return;
+ }
+ cur = g_list_next (cur);
+ }
+ cur = cache->static_items;
+ while (cur) {
+ item = cur->data;
+ if (strcmp (item->s->symbol, sym) == 0) {
+ item->metric_weight = weight;
+ return;
+ }
+ cur = g_list_next (cur);
+ }
+}
gboolean
validate_cache (struct symbols_cache *cache, struct config_file *cfg, gboolean strict)
@@ -787,6 +818,14 @@ validate_cache (struct symbols_cache *cache, struct config_file *cfg, gboolean s
g_list_free (metric_symbols);
#endif /* GLIB_COMPAT */
+ /* Now adjust symbol weights according to default metric */
+ if (cfg->default_metric != NULL) {
+ g_hash_table_foreach (cfg->default_metric->symbols, rspamd_symbols_cache_metric_cb, cache);
+ /* Resort caches */
+ cache->negative_items = g_list_sort (cache->negative_items, cache_logic_cmp);
+ cache->static_items = g_list_sort (cache->static_items, cache_logic_cmp);
+ }
+
return TRUE;
}
diff --git a/src/symbols_cache.h b/src/symbols_cache.h
index 15883f361..658852a32 100644
--- a/src/symbols_cache.h
+++ b/src/symbols_cache.h
@@ -43,6 +43,7 @@ struct cache_item {
/* Priority */
gint priority;
+ gdouble metric_weight;
};
diff --git a/src/ucl/include/ucl.h b/src/ucl/include/ucl.h
index f7390c9a7..632c6e170 100644
--- a/src/ucl/include/ucl.h
+++ b/src/ucl/include/ucl.h
@@ -104,7 +104,8 @@ enum ucl_type {
UCL_STRING, //!< UCL_STRING
UCL_BOOLEAN, //!< UCL_BOOLEAN
UCL_TIME, //!< UCL_TIME
- UCL_USERDATA //!< UCL_USERDATA
+ UCL_USERDATA, //!< UCL_USERDATA
+ UCL_NULL //!< UCL_NULL
};
/**
@@ -201,6 +202,26 @@ ucl_object_new (void)
if (new != NULL) {
memset (new, 0, sizeof (ucl_object_t));
new->ref = 1;
+ new->type = UCL_NULL;
+ }
+ return new;
+}
+
+/**
+ * Create new object with type specified
+ * @param type type of a new object
+ * @return new object
+ */
+static inline ucl_object_t* ucl_object_typed_new (unsigned int type) UCL_WARN_UNUSED_RESULT;
+static inline ucl_object_t *
+ucl_object_typed_new (unsigned int type)
+{
+ ucl_object_t *new;
+ new = malloc (sizeof (ucl_object_t));
+ if (new != NULL) {
+ memset (new, 0, sizeof (ucl_object_t));
+ new->ref = 1;
+ new->type = (type <= UCL_NULL ? type : UCL_NULL);
}
return new;
}
@@ -754,6 +775,16 @@ unsigned char *ucl_object_emit (ucl_object_t *obj, enum ucl_emitter emit_type);
*/
bool ucl_pubkey_add (struct ucl_parser *parser, const unsigned char *key, size_t len);
+/**
+ * Set FILENAME and CURDIR variables in parser
+ * @param parser parser object
+ * @param filename filename to set or NULL to set FILENAME to "undef" and CURDIR to getcwd()
+ * @param need_expand perform realpath() if this variable is true and filename is not NULL
+ * @return true if variables has been set
+ */
+bool ucl_parser_set_filevars (struct ucl_parser *parser, const char *filename,
+ bool need_expand);
+
typedef void* ucl_object_iter_t;
/**
diff --git a/src/ucl/src/ucl_emitter.c b/src/ucl/src/ucl_emitter.c
index c7d14dc8a..d0e62436d 100644
--- a/src/ucl/src/ucl_emitter.c
+++ b/src/ucl/src/ucl_emitter.c
@@ -38,6 +38,7 @@ static void ucl_elt_write_rcl (ucl_object_t *obj, UT_string *buf, unsigned int t
bool start_tabs, bool is_top, bool expand_array);
static void ucl_elt_write_yaml (ucl_object_t *obj, UT_string *buf, unsigned int tabs,
bool start_tabs, bool compact, bool expand_array);
+static void ucl_elt_array_write_yaml (ucl_object_t *obj, UT_string *buf, unsigned int tabs, bool start_tabs, bool is_top);
/**
* Add tabulation to the output buffer
@@ -256,6 +257,12 @@ ucl_elt_write_json (ucl_object_t *obj, UT_string *buf, unsigned int tabs, bool s
}
ucl_elt_string_write_json (obj->value.sv, obj->len, buf);
break;
+ case UCL_NULL:
+ if (start_tabs) {
+ ucl_add_tabs (buf, tabs, compact);
+ }
+ utstring_printf (buf, "null");
+ break;
case UCL_OBJECT:
ucl_elt_obj_write_json (obj, buf, tabs, start_tabs, compact);
break;
@@ -440,6 +447,12 @@ ucl_elt_write_rcl (ucl_object_t *obj, UT_string *buf, unsigned int tabs,
}
ucl_elt_string_write_json (obj->value.sv, obj->len, buf);
break;
+ case UCL_NULL:
+ if (start_tabs) {
+ ucl_add_tabs (buf, tabs, false);
+ }
+ utstring_printf (buf, "null");
+ break;
case UCL_OBJECT:
ucl_elt_obj_write_rcl (obj, buf, tabs, start_tabs, is_top);
break;
@@ -471,6 +484,19 @@ ucl_object_emit_rcl (ucl_object_t *obj)
}
+static void
+ucl_obj_write_yaml (ucl_object_t *obj, UT_string *buf, unsigned int tabs, bool start_tabs)
+{
+ bool is_array = (obj->next != NULL);
+
+ if (is_array) {
+ ucl_elt_array_write_yaml (obj, buf, tabs, start_tabs, false);
+ }
+ else {
+ ucl_elt_write_yaml(obj, buf, tabs, start_tabs, false, true);
+ }
+}
+
/**
* Write a single object to the buffer
* @param obj object to write
@@ -486,25 +512,20 @@ ucl_elt_obj_write_yaml (ucl_object_t *obj, UT_string *buf, unsigned int tabs, bo
ucl_add_tabs (buf, tabs, is_top);
}
if (!is_top) {
- utstring_append_len (buf, ": {\n", 4);
+ utstring_append_len (buf, "{\n", 2);
}
while ((cur = ucl_hash_iterate (obj->value.ov, &it))) {
ucl_add_tabs (buf, tabs + 1, is_top);
- if (cur->flags & UCL_OBJECT_NEED_KEY_ESCAPE) {
+ if (cur->keylen > 0) {
ucl_elt_string_write_json (cur->key, cur->keylen, buf);
}
else {
- utstring_append_len (buf, cur->key, cur->keylen);
- }
- if (cur->type != UCL_OBJECT && cur->type != UCL_ARRAY) {
- utstring_append_len (buf, " : ", 3);
- }
- else {
- utstring_append_c (buf, ' ');
+ utstring_append_len (buf, "null", 4);
}
- ucl_elt_write_yaml (cur, buf, is_top ? tabs : tabs + 1, false, false, true);
- if (cur->type != UCL_OBJECT && cur->type != UCL_ARRAY) {
+ utstring_append_len(buf, ": ", 2);
+ ucl_obj_write_yaml (cur, buf, is_top ? tabs : tabs + 1, false);
+ if (ucl_hash_iter_has_next(it)) {
if (!is_top) {
utstring_append_len (buf, ",\n", 2);
}
@@ -586,6 +607,12 @@ ucl_elt_write_yaml (ucl_object_t *obj, UT_string *buf, unsigned int tabs,
}
ucl_elt_string_write_json (obj->value.sv, obj->len, buf);
break;
+ case UCL_NULL:
+ if (start_tabs) {
+ ucl_add_tabs (buf, tabs, false);
+ }
+ utstring_printf (buf, "null");
+ break;
case UCL_OBJECT:
ucl_elt_obj_write_yaml (obj, buf, tabs, start_tabs, is_top);
break;
diff --git a/src/ucl/src/ucl_hash.c b/src/ucl/src/ucl_hash.c
index d644da4f4..a3711deb8 100644
--- a/src/ucl/src/ucl_hash.c
+++ b/src/ucl/src/ucl_hash.c
@@ -96,6 +96,9 @@ ucl_hash_search (ucl_hash_t* hashlin, const char *key, unsigned keylen)
{
ucl_hash_node_t *found;
+ if (hashlin == NULL) {
+ return NULL;
+ }
HASH_FIND (hh, hashlin->buckets, key, keylen, found);
if (found) {
diff --git a/src/ucl/src/ucl_internal.h b/src/ucl/src/ucl_internal.h
index 21b1aa53e..78b52edbd 100644
--- a/src/ucl/src/ucl_internal.h
+++ b/src/ucl/src/ucl_internal.h
@@ -96,6 +96,7 @@ struct ucl_macro {
struct ucl_stack {
ucl_object_t *obj;
struct ucl_stack *next;
+ int level;
};
struct ucl_chunk {
@@ -209,33 +210,33 @@ ucl_maybe_parse_boolean (ucl_object_t *obj, const unsigned char *start, size_t l
bool ret = false, val = false;
if (len == 5) {
- if (tolower (p[0]) == 'f' && strncasecmp (p, "false", 5) == 0) {
+ if ((p[0] == 'f' || p[0] == 'F') && strncasecmp (p, "false", 5) == 0) {
ret = true;
val = false;
}
}
else if (len == 4) {
- if (tolower (p[0]) == 't' && strncasecmp (p, "true", 4) == 0) {
+ if ((p[0] == 't' || p[0] == 'T') && strncasecmp (p, "true", 4) == 0) {
ret = true;
val = true;
}
}
else if (len == 3) {
- if (tolower (p[0]) == 'y' && strncasecmp (p, "yes", 3) == 0) {
+ if ((p[0] == 'y' || p[0] == 'Y') && strncasecmp (p, "yes", 3) == 0) {
ret = true;
val = true;
}
- if (tolower (p[0]) == 'o' && strncasecmp (p, "off", 3) == 0) {
+ else if ((p[0] == 'o' || p[0] == 'O') && strncasecmp (p, "off", 3) == 0) {
ret = true;
val = false;
}
}
else if (len == 2) {
- if (tolower (p[0]) == 'n' && strncasecmp (p, "no", 2) == 0) {
+ if ((p[0] == 'n' || p[0] == 'N') && strncasecmp (p, "no", 2) == 0) {
ret = true;
val = false;
}
- else if (tolower (p[0]) == 'o' && strncasecmp (p, "on", 2) == 0) {
+ else if ((p[0] == 'o' || p[0] == 'O') && strncasecmp (p, "on", 2) == 0) {
ret = true;
val = true;
}
diff --git a/src/ucl/src/ucl_parser.c b/src/ucl/src/ucl_parser.c
index 2e4816874..0441a121c 100644
--- a/src/ucl/src/ucl_parser.c
+++ b/src/ucl/src/ucl_parser.c
@@ -86,10 +86,21 @@ ucl_chunk_restore_state (struct ucl_chunk *chunk, struct ucl_parser_saved_state
static inline void
ucl_set_err (struct ucl_chunk *chunk, int code, const char *str, UT_string **err)
{
- ucl_create_err (err, "error on line %d at column %d: '%s', character: '%c'",
- chunk->line, chunk->column, str, *chunk->pos);
+ if (isgraph (*chunk->pos)) {
+ ucl_create_err (err, "error on line %d at column %d: '%s', character: '%c'",
+ chunk->line, chunk->column, str, *chunk->pos);
+ }
+ else {
+ ucl_create_err (err, "error on line %d at column %d: '%s', character: '0x%02x'",
+ chunk->line, chunk->column, str, (int)*chunk->pos);
+ }
}
+/**
+ * Skip all comments from the current pos resolving nested and multiline comments
+ * @param parser
+ * @return
+ */
static bool
ucl_skip_comments (struct ucl_parser *parser)
{
@@ -233,6 +244,16 @@ ucl_lex_is_comment (const unsigned char c1, const unsigned char c2)
return false;
}
+/**
+ * Check variable found
+ * @param parser
+ * @param ptr
+ * @param remain
+ * @param out_len
+ * @param strict
+ * @param found
+ * @return
+ */
static inline const char *
ucl_check_variable_safe (struct ucl_parser *parser, const char *ptr, size_t remain,
size_t *out_len, bool strict, bool *found)
@@ -263,6 +284,15 @@ ucl_check_variable_safe (struct ucl_parser *parser, const char *ptr, size_t rema
return ptr;
}
+/**
+ * Check for a variable in a given string
+ * @param parser
+ * @param ptr
+ * @param remain
+ * @param out_len
+ * @param vars_found
+ * @return
+ */
static const char *
ucl_check_variable (struct ucl_parser *parser, const char *ptr, size_t remain, size_t *out_len, bool *vars_found)
{
@@ -309,6 +339,14 @@ ucl_check_variable (struct ucl_parser *parser, const char *ptr, size_t remain, s
return ret;
}
+/**
+ * Expand a single variable
+ * @param parser
+ * @param ptr
+ * @param remain
+ * @param dest
+ * @return
+ */
static const char *
ucl_expand_single_variable (struct ucl_parser *parser, const char *ptr,
size_t remain, unsigned char **dest)
@@ -353,6 +391,14 @@ ucl_expand_single_variable (struct ucl_parser *parser, const char *ptr,
return ret;
}
+/**
+ * Expand variables in string
+ * @param parser
+ * @param dst
+ * @param src
+ * @param in_len
+ * @return
+ */
static ssize_t
ucl_expand_variable (struct ucl_parser *parser, unsigned char **dst,
const char *src, size_t in_len)
@@ -400,6 +446,18 @@ ucl_expand_variable (struct ucl_parser *parser, unsigned char **dst,
return out_len;
}
+/**
+ * Store or copy pointer to the trash stack
+ * @param parser parser object
+ * @param src src string
+ * @param dst destination buffer (trash stack pointer)
+ * @param dst_const const destination pointer (e.g. value of object)
+ * @param in_len input length
+ * @param need_unescape need to unescape source (and copy it)
+ * @param need_lowercase need to lowercase value (and copy)
+ * @param need_expand need to expand variables (and copy as well)
+ * @return output length (excluding \0 symbol)
+ */
static inline ssize_t
ucl_copy_or_store_ptr (struct ucl_parser *parser,
const unsigned char *src, unsigned char **dst,
@@ -448,6 +506,47 @@ ucl_copy_or_store_ptr (struct ucl_parser *parser,
return ret;
}
+/**
+ * Create and append an object at the specified level
+ * @param parser
+ * @param is_array
+ * @param level
+ * @return
+ */
+static inline ucl_object_t *
+ucl_add_parser_stack (ucl_object_t *obj, struct ucl_parser *parser, bool is_array, int level)
+{
+ struct ucl_stack *st;
+
+ if (!is_array) {
+ if (obj == NULL) {
+ obj = ucl_object_typed_new (UCL_OBJECT);
+ }
+ else {
+ obj->type = UCL_OBJECT;
+ }
+ obj->value.ov = ucl_hash_create ();
+ parser->state = UCL_STATE_KEY;
+ }
+ else {
+ if (obj == NULL) {
+ obj = ucl_object_typed_new (UCL_ARRAY);
+ }
+ else {
+ obj->type = UCL_ARRAY;
+ }
+ parser->state = UCL_STATE_VALUE;
+ }
+
+ st = UCL_ALLOC (sizeof (struct ucl_stack));
+ st->obj = obj;
+ st->level = level;
+ LL_PREPEND (parser->stack, st);
+ parser->cur_obj = obj;
+
+ return obj;
+}
+
int
ucl_maybe_parse_number (ucl_object_t *obj,
const char *start, const char *end, const char **pos, bool allow_double, bool number_bytes)
@@ -781,12 +880,13 @@ ucl_lex_json_string (struct ucl_parser *parser,
* @return true if a key has been parsed
*/
static bool
-ucl_parse_key (struct ucl_parser *parser, struct ucl_chunk *chunk)
+ucl_parse_key (struct ucl_parser *parser, struct ucl_chunk *chunk, bool *next_key, bool *end_of_object)
{
- const unsigned char *p, *c = NULL, *end;
+ const unsigned char *p, *c = NULL, *end, *t;
const char *key;
bool got_quote = false, got_eq = false, got_semicolon = false,
- need_unescape = false, ucl_escape = false, var_expand = false;
+ need_unescape = false, ucl_escape = false, var_expand = false,
+ got_content = false, got_sep = false;
ucl_object_t *nobj, *tobj;
ucl_hash_t *container;
ssize_t keylen;
@@ -805,23 +905,39 @@ ucl_parse_key (struct ucl_parser *parser, struct ucl_chunk *chunk)
* A key must start with alpha, number, '/' or '_' and end with space character
*/
if (c == NULL) {
- if (ucl_lex_is_comment (p[0], p[1])) {
+ if (chunk->remain >= 2 && ucl_lex_is_comment (p[0], p[1])) {
if (!ucl_skip_comments (parser)) {
return false;
}
p = chunk->pos;
}
+ else if (ucl_test_character (*p, UCL_CHARACTER_WHITESPACE_UNSAFE)) {
+ ucl_chunk_skipc (chunk, p);
+ }
else if (ucl_test_character (*p, UCL_CHARACTER_KEY_START)) {
/* The first symbol */
c = p;
ucl_chunk_skipc (chunk, p);
+ got_content = true;
}
else if (*p == '"') {
/* JSON style key */
c = p + 1;
got_quote = true;
+ got_content = true;
ucl_chunk_skipc (chunk, p);
}
+ else if (*p == '}') {
+ /* We have actually end of an object */
+ *end_of_object = true;
+ return true;
+ }
+ else if (*p == '.') {
+ ucl_chunk_skipc (chunk, p);
+ parser->prev_state = parser->state;
+ parser->state = UCL_STATE_MACRO_NAME;
+ return true;
+ }
else {
/* Invalid identifier */
ucl_set_err (chunk, UCL_ESYNTAX, "key must begin with a letter", &parser->err);
@@ -832,6 +948,7 @@ ucl_parse_key (struct ucl_parser *parser, struct ucl_chunk *chunk)
/* Parse the body of a key */
if (!got_quote) {
if (ucl_test_character (*p, UCL_CHARACTER_KEY)) {
+ got_content = true;
ucl_chunk_skipc (chunk, p);
}
else if (ucl_test_character (*p, UCL_CHARACTER_KEY_SEP)) {
@@ -856,11 +973,14 @@ ucl_parse_key (struct ucl_parser *parser, struct ucl_chunk *chunk)
}
}
- if (p >= chunk->end) {
+ if (p >= chunk->end && got_content) {
ucl_set_err (chunk, UCL_ESYNTAX, "unfinished key", &parser->err);
return false;
}
-
+ else if (!got_content) {
+ return true;
+ }
+ *end_of_object = false;
/* We are now at the end of the key, need to parse the rest */
while (p < chunk->end) {
if (ucl_test_character (*p, UCL_CHARACTER_WHITESPACE)) {
@@ -886,7 +1006,7 @@ ucl_parse_key (struct ucl_parser *parser, struct ucl_chunk *chunk)
return false;
}
}
- else if (ucl_lex_is_comment (p[0], p[1])) {
+ else if (chunk->remain >= 2 && ucl_lex_is_comment (p[0], p[1])) {
/* Check for comment */
if (!ucl_skip_comments (parser)) {
return false;
@@ -899,11 +1019,41 @@ ucl_parse_key (struct ucl_parser *parser, struct ucl_chunk *chunk)
}
}
- if (p >= chunk->end) {
+ if (p >= chunk->end && got_content) {
ucl_set_err (chunk, UCL_ESYNTAX, "unfinished key", &parser->err);
return false;
}
+ got_sep = got_semicolon || got_eq;
+
+ if (!got_sep) {
+ /*
+ * Maybe we have more keys nested, so search for termination character.
+ * Possible choices:
+ * 1) key1 key2 ... keyN [:=] value <- we treat that as error
+ * 2) key1 ... keyN {} or [] <- we treat that as nested objects
+ * 3) key1 value[;,\n] <- we treat that as linear object
+ */
+ t = p;
+ *next_key = false;
+ while (ucl_test_character (*t, UCL_CHARACTER_WHITESPACE)) {
+ t ++;
+ }
+ /* Check first non-space character after a key */
+ if (*t != '{' && *t != '[') {
+ while (t < chunk->end) {
+ if (*t == ',' || *t == ';' || *t == '\n' || *t == '\r') {
+ break;
+ }
+ else if (*t == '{' || *t == '[') {
+ *next_key = true;
+ break;
+ }
+ t ++;
+ }
+ }
+ }
+
/* Create a new object */
nobj = ucl_object_new ();
keylen = ucl_copy_or_store_ptr (parser, c, &nobj->trash_stack[UCL_TRASH_KEY],
@@ -989,7 +1139,7 @@ ucl_parse_string_value (struct ucl_parser *parser,
*var_expand = true;
}
- if (ucl_lex_is_atom_end (*p) || ucl_lex_is_comment (p[0], p[1])) {
+ if (ucl_lex_is_atom_end (*p) || (chunk->remain >= 2 && ucl_lex_is_comment (p[0], p[1]))) {
break;
}
ucl_chunk_skipc (chunk, p);
@@ -1064,7 +1214,6 @@ static bool
ucl_parse_value (struct ucl_parser *parser, struct ucl_chunk *chunk)
{
const unsigned char *p, *c;
- struct ucl_stack *st;
ucl_object_t *obj = NULL, *t;
unsigned int stripped_spaces;
int str_len;
@@ -1107,27 +1256,14 @@ ucl_parse_value (struct ucl_parser *parser, struct ucl_chunk *chunk)
break;
case '{':
/* We have a new object */
- obj->type = UCL_OBJECT;
- obj->value.ov = ucl_hash_create ();
- parser->state = UCL_STATE_KEY;
- st = UCL_ALLOC (sizeof (struct ucl_stack));
- st->obj = obj;
- LL_PREPEND (parser->stack, st);
- parser->cur_obj = obj;
+ obj = ucl_add_parser_stack (obj, parser, false, parser->stack->level);
ucl_chunk_skipc (chunk, p);
return true;
break;
case '[':
/* We have a new array */
- obj = parser->cur_obj;
- obj->type = UCL_ARRAY;
-
- parser->state = UCL_STATE_VALUE;
- st = UCL_ALLOC (sizeof (struct ucl_stack));
- st->obj = obj;
- LL_PREPEND (parser->stack, st);
- parser->cur_obj = obj;
+ obj = ucl_add_parser_stack (obj, parser, true, parser->stack->level);
ucl_chunk_skipc (chunk, p);
return true;
@@ -1168,7 +1304,7 @@ ucl_parse_value (struct ucl_parser *parser, struct ucl_chunk *chunk)
default:
/* Skip any spaces and comments */
if (ucl_test_character (*p, UCL_CHARACTER_WHITESPACE_UNSAFE) ||
- ucl_lex_is_comment (p[0], p[1])) {
+ (chunk->remain >= 2 && ucl_lex_is_comment (p[0], p[1]))) {
while (p < chunk->end && ucl_test_character (*p, UCL_CHARACTER_WHITESPACE_UNSAFE)) {
ucl_chunk_skipc (chunk, p);
}
@@ -1206,8 +1342,11 @@ ucl_parse_value (struct ucl_parser *parser, struct ucl_chunk *chunk)
ucl_set_err (chunk, 0, "string value must not be empty", &parser->err);
return false;
}
-
- if (!ucl_maybe_parse_boolean (obj, c, str_len)) {
+ else if (str_len == 4 && memcmp (c, "null", 4) == 0) {
+ obj->len = 0;
+ obj->type = UCL_NULL;
+ }
+ else if (!ucl_maybe_parse_boolean (obj, c, str_len)) {
obj->type = UCL_STRING;
if ((str_len = ucl_copy_or_store_ptr (parser, c, &obj->trash_stack[UCL_TRASH_VALUE],
&obj->value.sv, str_len, false, false, var_expand)) == -1) {
@@ -1238,6 +1377,7 @@ ucl_parse_after_value (struct ucl_parser *parser, struct ucl_chunk *chunk)
const unsigned char *p;
bool got_sep = false;
struct ucl_stack *st;
+ int last_level;
p = chunk->pos;
@@ -1246,7 +1386,7 @@ ucl_parse_after_value (struct ucl_parser *parser, struct ucl_chunk *chunk)
/* Skip whitespaces */
ucl_chunk_skipc (chunk, p);
}
- else if (ucl_lex_is_comment (p[0], p[1])) {
+ else if (chunk->remain >= 2 && ucl_lex_is_comment (p[0], p[1])) {
/* Skip comment */
if (!ucl_skip_comments (parser)) {
return false;
@@ -1258,16 +1398,24 @@ ucl_parse_after_value (struct ucl_parser *parser, struct ucl_chunk *chunk)
else if (ucl_test_character (*p, UCL_CHARACTER_VALUE_END)) {
if (*p == '}' || *p == ']') {
if (parser->stack == NULL) {
- ucl_set_err (chunk, UCL_ESYNTAX, "unexpected } detected", &parser->err);
+ ucl_set_err (chunk, UCL_ESYNTAX, "end of array or object detected without corresponding start", &parser->err);
return false;
}
if ((*p == '}' && parser->stack->obj->type == UCL_OBJECT) ||
(*p == ']' && parser->stack->obj->type == UCL_ARRAY)) {
- /* Pop object from a stack */
+ /* Pop all nested objects from a stack */
st = parser->stack;
+ last_level = st->level;
parser->stack = st->next;
UCL_FREE (sizeof (struct ucl_stack), st);
+
+ while (parser->stack != NULL && last_level > 0 && parser->stack->level == last_level) {
+ st = parser->stack;
+ parser->stack = st->next;
+ last_level = st->level;
+ UCL_FREE (sizeof (struct ucl_stack), st);
+ }
}
else {
ucl_set_err (chunk, UCL_ESYNTAX, "unexpected terminating symbol detected", &parser->err);
@@ -1391,11 +1539,23 @@ ucl_state_machine (struct ucl_parser *parser)
{
ucl_object_t *obj;
struct ucl_chunk *chunk = parser->chunks;
- struct ucl_stack *st;
const unsigned char *p, *c = NULL, *macro_start = NULL;
unsigned char *macro_escaped;
size_t macro_len = 0;
struct ucl_macro *macro = NULL;
+ bool next_key = false, end_of_object = false;
+
+ if (parser->top_obj == NULL) {
+ if (*chunk->pos == '[') {
+ obj = ucl_add_parser_stack (NULL, parser, true, 0);
+ }
+ else {
+ obj = ucl_add_parser_stack (NULL, parser, false, 0);
+ }
+ parser->top_obj = obj;
+ parser->cur_obj = obj;
+ parser->state = UCL_STATE_INIT;
+ }
p = chunk->pos;
while (chunk->pos < chunk->end) {
@@ -1406,6 +1566,7 @@ ucl_state_machine (struct ucl_parser *parser)
* if we got [ or { correspondingly or can just treat new data as
* a key of newly created object
*/
+ obj = parser->cur_obj;
if (!ucl_skip_comments (parser)) {
parser->prev_state = parser->state;
parser->state = UCL_STATE_ERROR;
@@ -1413,25 +1574,16 @@ ucl_state_machine (struct ucl_parser *parser)
}
else {
p = chunk->pos;
- obj = ucl_object_new ();
if (*p == '[') {
parser->state = UCL_STATE_VALUE;
- obj->type = UCL_ARRAY;
ucl_chunk_skipc (chunk, p);
}
else {
parser->state = UCL_STATE_KEY;
- obj->type = UCL_OBJECT;
- obj->value.ov = ucl_hash_create ();
if (*p == '{') {
ucl_chunk_skipc (chunk, p);
}
- };
- parser->cur_obj = obj;
- parser->top_obj = obj;
- st = UCL_ALLOC (sizeof (struct ucl_stack));
- st->obj = obj;
- LL_PREPEND (parser->stack, st);
+ }
}
break;
case UCL_STATE_KEY:
@@ -1444,13 +1596,24 @@ ucl_state_machine (struct ucl_parser *parser)
parser->state = UCL_STATE_AFTER_VALUE;
continue;
}
- if (!ucl_parse_key (parser, chunk)) {
+ if (!ucl_parse_key (parser, chunk, &next_key, &end_of_object)) {
parser->prev_state = parser->state;
parser->state = UCL_STATE_ERROR;
return false;
}
- if (parser->state != UCL_STATE_MACRO_NAME) {
- parser->state = UCL_STATE_VALUE;
+ if (end_of_object) {
+ p = chunk->pos;
+ parser->state = UCL_STATE_AFTER_VALUE;
+ continue;
+ }
+ else if (parser->state != UCL_STATE_MACRO_NAME) {
+ if (next_key && parser->stack->obj->type == UCL_OBJECT) {
+ /* Parse more keys and nest objects accordingly */
+ obj = ucl_add_parser_stack (parser->cur_obj, parser, false, parser->stack->level + 1);
+ }
+ else {
+ parser->state = UCL_STATE_VALUE;
+ }
}
else {
c = chunk->pos;
@@ -1504,7 +1667,7 @@ ucl_state_machine (struct ucl_parser *parser)
/* Now we need to skip all spaces */
while (p < chunk->end) {
if (!ucl_test_character (*p, UCL_CHARACTER_WHITESPACE_UNSAFE)) {
- if (ucl_lex_is_comment (p[0], p[1])) {
+ if (chunk->remain >= 2 && ucl_lex_is_comment (p[0], p[1])) {
/* Skip comment */
if (!ucl_skip_comments (parser)) {
return false;
@@ -1526,8 +1689,8 @@ ucl_state_machine (struct ucl_parser *parser)
return false;
}
macro_len = ucl_expand_variable (parser, &macro_escaped, macro_start, macro_len);
- parser->state = UCL_STATE_AFTER_VALUE;
- if (macro_escaped == macro_start) {
+ parser->state = parser->prev_state;
+ if (macro_escaped == NULL) {
if (!macro->handler (macro_start, macro_len, macro->ud)) {
return false;
}
@@ -1565,6 +1728,9 @@ ucl_parser_new (int flags)
new->flags = flags;
+ /* Initial assumption about filevars */
+ ucl_parser_set_filevars (new, NULL, false);
+
return new;
}
@@ -1587,16 +1753,51 @@ void
ucl_parser_register_variable (struct ucl_parser *parser, const char *var,
const char *value)
{
- struct ucl_variable *new;
+ struct ucl_variable *new = NULL, *cur;
- new = UCL_ALLOC (sizeof (struct ucl_variable));
- memset (new, 0, sizeof (struct ucl_variable));
- new->var = strdup (var);
- new->var_len = strlen (var);
- new->value = strdup (value);
- new->value_len = strlen (value);
+ if (var == NULL) {
+ return;
+ }
- LL_PREPEND (parser->variables, new);
+ /* Find whether a variable already exists */
+ LL_FOREACH (parser->variables, cur) {
+ if (strcmp (cur->var, var) == 0) {
+ new = cur;
+ break;
+ }
+ }
+
+ if (value == NULL) {
+
+ if (new != NULL) {
+ /* Remove variable */
+ LL_DELETE (parser->variables, new);
+ free (new->var);
+ free (new->value);
+ UCL_FREE (sizeof (struct ucl_variable), new);
+ }
+ else {
+ /* Do nothing */
+ return;
+ }
+ }
+ else {
+ if (new == NULL) {
+ new = UCL_ALLOC (sizeof (struct ucl_variable));
+ memset (new, 0, sizeof (struct ucl_variable));
+ new->var = strdup (var);
+ new->var_len = strlen (var);
+ new->value = strdup (value);
+ new->value_len = strlen (value);
+
+ LL_PREPEND (parser->variables, new);
+ }
+ else {
+ free (new->value);
+ new->value = strdup (value);
+ new->value_len = strlen (value);
+ }
+ }
}
bool
diff --git a/src/ucl/src/ucl_util.c b/src/ucl/src/ucl_util.c
index 0df611961..470d29163 100644
--- a/src/ucl/src/ucl_util.c
+++ b/src/ucl/src/ucl_util.c
@@ -25,6 +25,8 @@
#include "ucl_internal.h"
#include "ucl_chartable.h"
+#include <libgen.h> /* For dirname */
+
#ifdef HAVE_OPENSSL
#include <openssl/err.h>
#include <openssl/sha.h>
@@ -224,7 +226,7 @@ ucl_copy_value_trash (ucl_object_t *obj)
ucl_object_t*
ucl_parser_get_object (struct ucl_parser *parser)
{
- if (parser->state != UCL_STATE_INIT && parser->state != UCL_STATE_ERROR) {
+ if (parser->state != UCL_STATE_ERROR) {
return ucl_object_ref (parser->top_obj);
}
@@ -288,7 +290,7 @@ ucl_pubkey_add (struct ucl_parser *parser, const unsigned char *key, size_t len)
return false;
#else
# if (OPENSSL_VERSION_NUMBER < 0x10000000L)
- ucl_create_err (err, "cannot check signatures, openssl version is unsupported");
+ ucl_create_err (&parser->err, "cannot check signatures, openssl version is unsupported");
return EXIT_FAILURE;
# else
struct ucl_pubkey *nkey;
@@ -439,24 +441,31 @@ ucl_fetch_file (const unsigned char *filename, unsigned char **buf, size_t *bufl
int fd;
struct stat st;
- if (stat (filename, &st) == -1) {
+ if (stat (filename, &st) == -1 || !S_ISREG (st.st_mode)) {
ucl_create_err (err, "cannot stat file %s: %s",
filename, strerror (errno));
return false;
}
- if ((fd = open (filename, O_RDONLY)) == -1) {
- ucl_create_err (err, "cannot open file %s: %s",
- filename, strerror (errno));
- return false;
+ if (st.st_size == 0) {
+ /* Do not map empty files */
+ *buf = "";
+ *buflen = 0;
}
- if ((*buf = mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) {
+ else {
+ if ((fd = open (filename, O_RDONLY)) == -1) {
+ ucl_create_err (err, "cannot open file %s: %s",
+ filename, strerror (errno));
+ return false;
+ }
+ if ((*buf = mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) {
+ close (fd);
+ ucl_create_err (err, "cannot mmap file %s: %s",
+ filename, strerror (errno));
+ return false;
+ }
+ *buflen = st.st_size;
close (fd);
- ucl_create_err (err, "cannot mmap file %s: %s",
- filename, strerror (errno));
- return false;
}
- *buflen = st.st_size;
- close (fd);
return true;
}
@@ -528,6 +537,7 @@ ucl_include_url (const unsigned char *data, size_t len,
size_t buflen = 0;
struct ucl_chunk *chunk;
char urlbuf[PATH_MAX];
+ int prev_state;
snprintf (urlbuf, sizeof (urlbuf), "%.*s", (int)len, data);
@@ -548,13 +558,20 @@ ucl_include_url (const unsigned char *data, size_t len,
ucl_create_err (&parser->err, "cannot verify url %s: %s",
urlbuf,
ERR_error_string (ERR_get_error (), NULL));
- munmap (sigbuf, siglen);
+ if (siglen > 0) {
+ munmap (sigbuf, siglen);
+ }
return false;
}
- munmap (sigbuf, siglen);
+ if (siglen > 0) {
+ munmap (sigbuf, siglen);
+ }
#endif
}
+ prev_state = parser->state;
+ parser->state = UCL_STATE_INIT;
+
res = ucl_parser_add_chunk (parser, buf, buflen);
if (res == true) {
/* Remove chunk from the stack */
@@ -564,6 +581,8 @@ ucl_include_url (const unsigned char *data, size_t len,
UCL_FREE (sizeof (struct ucl_chunk), chunk);
}
}
+
+ parser->state = prev_state;
free (buf);
return res;
@@ -586,6 +605,7 @@ ucl_include_file (const unsigned char *data, size_t len,
unsigned char *buf = NULL;
size_t buflen;
char filebuf[PATH_MAX], realbuf[PATH_MAX];
+ int prev_state;
snprintf (filebuf, sizeof (filebuf), "%.*s", (int)len, data);
if (realpath (filebuf, realbuf) == NULL) {
@@ -612,13 +632,22 @@ ucl_include_file (const unsigned char *data, size_t len,
ucl_create_err (&parser->err, "cannot verify file %s: %s",
filebuf,
ERR_error_string (ERR_get_error (), NULL));
- munmap (sigbuf, siglen);
+ if (siglen > 0) {
+ munmap (sigbuf, siglen);
+ }
return false;
}
- munmap (sigbuf, siglen);
+ if (siglen > 0) {
+ munmap (sigbuf, siglen);
+ }
#endif
}
+ ucl_parser_set_filevars (parser, realbuf, false);
+
+ prev_state = parser->state;
+ parser->state = UCL_STATE_INIT;
+
res = ucl_parser_add_chunk (parser, buf, buflen);
if (res == true) {
/* Remove chunk from the stack */
@@ -628,7 +657,12 @@ ucl_include_file (const unsigned char *data, size_t len,
UCL_FREE (sizeof (struct ucl_chunk), chunk);
}
}
- munmap (buf, buflen);
+
+ parser->state = prev_state;
+
+ if (buflen > 0) {
+ munmap (buf, buflen);
+ }
return res;
}
@@ -676,19 +710,60 @@ ucl_includes_handler (const unsigned char *data, size_t len, void* ud)
}
bool
+ucl_parser_set_filevars (struct ucl_parser *parser, const char *filename, bool need_expand)
+{
+ char realbuf[PATH_MAX], *curdir;
+
+ if (filename != NULL) {
+ if (need_expand) {
+ if (realpath (filename, realbuf) == NULL) {
+ return false;
+ }
+ }
+ else {
+ ucl_strlcpy (realbuf, filename, sizeof (realbuf));
+ }
+
+ /* Define variables */
+ ucl_parser_register_variable (parser, "FILENAME", realbuf);
+ curdir = dirname (realbuf);
+ ucl_parser_register_variable (parser, "CURDIR", curdir);
+ }
+ else {
+ /* Set everything from the current dir */
+ curdir = getcwd (realbuf, sizeof (realbuf));
+ ucl_parser_register_variable (parser, "FILENAME", "undef");
+ ucl_parser_register_variable (parser, "CURDIR", curdir);
+ }
+
+ return true;
+}
+
+bool
ucl_parser_add_file (struct ucl_parser *parser, const char *filename)
{
unsigned char *buf;
size_t len;
bool ret;
+ char realbuf[PATH_MAX];
+
+ if (realpath (filename, realbuf) == NULL) {
+ ucl_create_err (&parser->err, "cannot open file %s: %s",
+ filename,
+ strerror (errno));
+ return false;
+ }
- if (!ucl_fetch_file (filename, &buf, &len, &parser->err)) {
+ if (!ucl_fetch_file (realbuf, &buf, &len, &parser->err)) {
return false;
}
+ ucl_parser_set_filevars (parser, realbuf, false);
ret = ucl_parser_add_chunk (parser, buf, len);
- munmap (buf, len);
+ if (len > 0) {
+ munmap (buf, len);
+ }
return ret;
}
@@ -883,6 +958,17 @@ ucl_object_insert_key_common (ucl_object_t *top, ucl_object_t *elt,
top->type = UCL_OBJECT;
}
+ if (top->type != UCL_OBJECT) {
+ /* It is possible to convert NULL type to an object */
+ if (top->type == UCL_NULL) {
+ top->type = UCL_OBJECT;
+ }
+ else {
+ /* Refuse converting of other object types */
+ return top;
+ }
+ }
+
if (top->value.ov == NULL) {
top->value.ov = ucl_hash_create ();
}
diff --git a/src/ucl/src/xxhash.c b/src/ucl/src/xxhash.c
index bb4c639aa..5869503be 100644
--- a/src/ucl/src/xxhash.c
+++ b/src/ucl/src/xxhash.c
@@ -273,7 +273,7 @@ U32 XXH32(const void* input, int len, U32 seed)
XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN;
# if !defined(XXH_USE_UNALIGNED_ACCESS)
- if ((((size_t)input) & 3)) // Input is aligned, let's leverage the speed advantage
+ if (!(((size_t)input) & 3)) // Input is aligned, let's leverage the speed advantage
{
if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT)
return XXH32_endian_align(input, len, seed, XXH_littleEndian, XXH_aligned);
diff --git a/src/ucl/tests/rcl_test.json.xz b/src/ucl/tests/rcl_test.json.xz
deleted file mode 100644
index 98c3559ad..000000000
--- a/src/ucl/tests/rcl_test.json.xz
+++ /dev/null
Binary files differ
diff --git a/src/url.c b/src/url.c
index d1a85b32a..debe41aea 100644
--- a/src/url.c
+++ b/src/url.c
@@ -371,7 +371,7 @@ struct url_matcher matchers[] = {
{ ".zm", "http://", url_tld_start, url_tld_end, URL_FLAG_NOHTML | URL_FLAG_STRICT_MATCH },
{ ".zw", "http://", url_tld_start, url_tld_end, URL_FLAG_NOHTML | URL_FLAG_STRICT_MATCH },
/* Likely emails */
- { "@", "mailto://",url_email_start, url_email_end, URL_FLAG_NOHTML | URL_FLAG_STRICT_MATCH }
+ { "@", "mailto://",url_email_start, url_email_end, URL_FLAG_NOHTML }
};
struct url_match_scanner {
@@ -1456,7 +1456,7 @@ url_email_start (const gchar *begin, const gchar *end, const gchar *pos, url_mat
else {
p = pos + strlen (match->pattern);
if (is_domain (*p)) {
- match->m_begin = p;
+ match->m_begin = pos;
return TRUE;
}
}
@@ -1467,15 +1467,24 @@ static gboolean
url_email_end (const gchar *begin, const gchar *end, const gchar *pos, url_match_t *match)
{
const gchar *p;
+ gboolean got_at = FALSE;
p = pos + strlen (match->pattern);
+ if (*pos == '@') {
+ got_at = TRUE;
+ }
- while (p < end && (is_domain (*p) || *p == '_' || (*p == '.' && p + 1 < end && is_domain (*(p + 1))))) {
+ while (p < end && (is_domain (*p) || *p == '_'
+ || (*p == '@' && !got_at) ||
+ (*p == '.' && p + 1 < end && is_domain (*(p + 1))))) {
+ if (*p == '@') {
+ got_at = TRUE;
+ }
p ++;
}
match->m_len = p - match->m_begin;
match->add_prefix = TRUE;
- return TRUE;
+ return got_at;
}
void
diff --git a/src/util.c b/src/util.c
index bc2206579..776e807d1 100644
--- a/src/util.c
+++ b/src/util.c
@@ -31,6 +31,11 @@
#include "filter.h"
#include "message.h"
+#ifdef HAVE_OPENSSL
+#include <openssl/rand.h>
+#include <openssl/err.h>
+#endif
+
/* Check log messages intensity once per minute */
#define CHECK_TIME 60
/* More than 2 log messages per second */
@@ -215,7 +220,8 @@ accept_from_socket (gint listen_sock, struct sockaddr *addr, socklen_t * len)
gint
make_unix_socket (const gchar *path, struct sockaddr_un *addr, gint type, gboolean is_server, gboolean async)
{
- gint fd, s_error, r, optlen, serrno, on = 1;
+ gint fd = -1, s_error, r, optlen, serrno, on = 1;
+ struct stat st;
if (path == NULL)
return -1;
@@ -227,10 +233,25 @@ make_unix_socket (const gchar *path, struct sockaddr_un *addr, gint type, gboole
addr->sun_len = SUN_LEN (addr);
#endif
+ if (is_server) {
+ /* Unlink socket if it exists already */
+ if (lstat (addr->sun_path, &st) != -1) {
+ if (S_ISSOCK (st.st_mode)) {
+ if (unlink (addr->sun_path) == -1) {
+ msg_warn ("unlink %s failed: %d, '%s'", addr->sun_path, errno, strerror (errno));
+ goto out;
+ }
+ }
+ else {
+ msg_warn ("%s is not a socket", addr->sun_path);
+ goto out;
+ }
+ }
+ }
fd = socket (PF_LOCAL, type, 0);
if (fd == -1) {
- msg_warn ("socket failed: %d, '%s'", errno, strerror (errno));
+ msg_warn ("socket failed %s: %d, '%s'", addr->sun_path, errno, strerror (errno));
return -1;
}
@@ -240,7 +261,7 @@ make_unix_socket (const gchar *path, struct sockaddr_un *addr, gint type, gboole
/* Set close on exec */
if (fcntl (fd, F_SETFD, FD_CLOEXEC) == -1) {
- msg_warn ("fcntl failed: %d, '%s'", errno, strerror (errno));
+ msg_warn ("fcntl failed %s: %d, '%s'", addr->sun_path, errno, strerror (errno));
goto out;
}
if (is_server) {
@@ -253,14 +274,14 @@ make_unix_socket (const gchar *path, struct sockaddr_un *addr, gint type, gboole
if (r == -1) {
if (errno != EINPROGRESS) {
- msg_warn ("bind/connect failed: %d, '%s'", errno, strerror (errno));
+ msg_warn ("bind/connect failed %s: %d, '%s'", addr->sun_path, errno, strerror (errno));
goto out;
}
if (!async) {
/* Try to poll */
if (poll_sync_socket (fd, CONNECT_TIMEOUT * 1000, POLLOUT) <= 0) {
errno = ETIMEDOUT;
- msg_warn ("bind/connect failed: timeout");
+ msg_warn ("bind/connect failed %s: timeout", addr->sun_path);
goto out;
}
else {
@@ -286,7 +307,9 @@ make_unix_socket (const gchar *path, struct sockaddr_un *addr, gint type, gboole
out:
serrno = errno;
- close (fd);
+ if (fd != -1) {
+ close (fd);
+ }
errno = serrno;
return (-1);
}
@@ -310,18 +333,11 @@ make_universal_socket (const gchar *credits, guint16 port,
gchar portbuf[8];
if (*credits == '/') {
- r = stat (credits, &st);
if (is_server) {
- if (r == -1) {
- return make_unix_socket (credits, &un, type, is_server, async);
- }
- else {
- /* Unix socket exists, it must be unlinked first */
- errno = EEXIST;
- return -1;
- }
+ return make_unix_socket (credits, &un, type, is_server, async);
}
else {
+ r = stat (credits, &st);
if (r == -1) {
/* Unix socket doesn't exists it must be created first */
errno = ENOENT;
@@ -394,18 +410,11 @@ make_universal_sockets_list (const gchar *credits, guint16 port,
cur = strv;
while (*cur != NULL) {
if (*credits == '/') {
- r = stat (credits, &st);
if (is_server) {
- if (r == -1) {
- fd = make_unix_socket (credits, &un, type, is_server, async);
- }
- else {
- /* Unix socket exists, it must be unlinked first */
- errno = EEXIST;
- goto err;
- }
+ fd = make_unix_socket (credits, &un, type, is_server, async);
}
else {
+ r = stat (credits, &st);
if (r == -1) {
/* Unix socket doesn't exists it must be created first */
errno = ENOENT;
@@ -2386,6 +2395,51 @@ restart:
return p - buf;
#endif
}
+
+void
+rspamd_random_bytes (gchar *buf, gsize buflen)
+{
+ gint fd;
+ gsize i;
+#ifdef HAVE_OPENSSL
+
+ /* Init random generator */
+ if (RAND_bytes (buf, buflen) != 1) {
+ msg_err ("cannot seed random generator using openssl: %s, using time",
+ ERR_error_string (ERR_get_error (), NULL));
+ goto fallback;
+ }
+#else
+ goto fallback;
+#endif
+ return;
+
+fallback:
+ /* Try to use /dev/random if no openssl is found */
+ fd = open ("/dev/random", O_RDONLY);
+ if (fd != -1) {
+ if (read (fd, buf, buflen) == (gssize)buflen) {
+ close (fd);
+ return;
+ }
+ close (fd);
+ }
+ /* No /dev/random */
+ g_random_set_seed (time (NULL));
+ for (i = 0; i < buflen; i ++) {
+ buf[i] = g_random_int () & 0xff;
+ }
+}
+
+void
+rspamd_prng_seed (void)
+{
+ guint32 rand_seed = 0;
+
+ rspamd_random_bytes ((gchar *)&rand_seed, sizeof (rand_seed));
+ g_random_set_seed (rand_seed);
+}
+
/*
* vi:ts=4
*/
diff --git a/src/util.h b/src/util.h
index f2b7132af..c36587cab 100644
--- a/src/util.h
+++ b/src/util.h
@@ -14,6 +14,17 @@ struct workq;
struct statfile;
struct classifier_config;
+/**
+ * Union that is used for storing sockaddrs
+ */
+union sa_union {
+ struct sockaddr_storage ss;
+ struct sockaddr sa;
+ struct sockaddr_in s4;
+ struct sockaddr_in6 s6;
+ struct sockaddr_un su;
+};
+
/*
* Create socket and bind or connect it to specified address and port
*/
@@ -440,16 +451,15 @@ time_t parse_http_date (const gchar *header, gsize len);
gint rspamd_read_passphrase (gchar *buf, gint size, gint rwflag, gpointer key);
/**
- * Expand path that may contain configuration variables:
- * $CONFDIR - configuration directory
- * $RUNDIR - local states directory
- * $DBDIR - databases dir
- * $LOGDIR - logs dir
- * $PLUGINSDIR - plugins dir
- * $PREFIX - installation prefix
- * $VERSION - rspamd version
- * @param pool to use
- * @param path path to expand
+ * Seed glib prng using openssl if possible
+ */
+void rspamd_prng_seed (void);
+
+/**
+ * Generate random bytes using the most suitable generator
+ * @param buf
+ * @param buflen
*/
+void rspamd_random_bytes (gchar *buf, gsize buflen);
#endif
diff --git a/src/worker.c b/src/worker.c
index bb43afba8..95355bdd3 100644
--- a/src/worker.c
+++ b/src/worker.c
@@ -502,8 +502,9 @@ accept_socket (gint fd, short what, void *arg)
struct rspamd_worker_ctx *ctx;
union sa_union su;
struct worker_task *new_task;
+ char ip_str[INET6_ADDRSTRLEN + 1];
- socklen_t addrlen = sizeof (su.ss);
+ socklen_t addrlen = sizeof (su);
gint nfd;
ctx = worker->ctx;
@@ -514,7 +515,7 @@ accept_socket (gint fd, short what, void *arg)
}
if ((nfd =
- accept_from_socket (fd, (struct sockaddr *) &su.ss, &addrlen)) == -1) {
+ accept_from_socket (fd, &su.sa, &addrlen)) == -1) {
msg_warn ("accept failed: %s", strerror (errno));
return;
}
@@ -525,16 +526,21 @@ accept_socket (gint fd, short what, void *arg)
new_task = construct_task (worker);
- if (su.ss.ss_family == AF_UNIX) {
+ if (su.sa.sa_family == AF_UNIX) {
msg_info ("accepted connection from unix socket");
new_task->client_addr.s_addr = INADDR_NONE;
}
- else if (su.ss.ss_family == AF_INET) {
+ else if (su.sa.sa_family == AF_INET) {
msg_info ("accepted connection from %s port %d",
inet_ntoa (su.s4.sin_addr), ntohs (su.s4.sin_port));
memcpy (&new_task->client_addr, &su.s4.sin_addr,
sizeof (struct in_addr));
}
+ else if (su.sa.sa_family == AF_INET6) {
+ msg_info ("accepted connection from %s port %d",
+ inet_ntop (su.sa.sa_family, &su.s6.sin6_addr, ip_str, sizeof (ip_str)),
+ ntohs (su.s6.sin6_port));
+ }
/* Copy some variables */
new_task->sock = nfd;
diff --git a/src/worker_util.c b/src/worker_util.c
index e8d8f7423..599d098c9 100644
--- a/src/worker_util.c
+++ b/src/worker_util.c
@@ -152,7 +152,6 @@ free_task (struct worker_task *task, gboolean is_soft)
if (task->received) {
g_list_free (task->received);
}
- memory_pool_delete (task->task_pool);
if (task->dispatcher) {
if (is_soft) {
/* Plan dispatcher shutdown */
@@ -165,6 +164,7 @@ free_task (struct worker_task *task, gboolean is_soft)
if (task->sock != -1) {
close (task->sock);
}
+ memory_pool_delete (task->task_pool);
g_slice_free1 (sizeof (struct worker_task), task);
}
}