]> source.dussan.org Git - rspamd.git/commitdiff
* Avoid DoS while sending a message with a lot of recipients in 'To' header.
authorVsevolod Stakhov <vsevolod@rambler-co.ru>
Mon, 28 May 2012 13:26:38 +0000 (17:26 +0400)
committerVsevolod Stakhov <vsevolod@rambler-co.ru>
Mon, 28 May 2012 13:26:38 +0000 (17:26 +0400)
Fix possible deadlock in lua_redis.
Version is now 0.4.8 as there are enough changes.

CMakeLists.txt
conf/lua/regexp/headers.lua
src/lua/lua_redis.c
src/lua/lua_task.c
src/plugins/lua/ratelimit.lua

index fc15f86c8b7ca4605cae52f438b1c98b5c13ba58..690ac352d423b850470be1f686e22bccdf963696 100644 (file)
@@ -10,7 +10,7 @@ PROJECT(rspamd C)
 
 SET(RSPAMD_VERSION_MAJOR 0)
 SET(RSPAMD_VERSION_MINOR 4)
-SET(RSPAMD_VERSION_PATCH 7)
+SET(RSPAMD_VERSION_PATCH 8)
 
 
 SET(RSPAMD_VERSION         "${RSPAMD_VERSION_MAJOR}.${RSPAMD_VERSION_MINOR}.${RSPAMD_VERSION_PATCH}")
index 0af26646d5086ef310ed4c52ae62c1b4b54e85a8..11460961da5b7d5b1b886d1a320027d10a21c0c5 100644 (file)
@@ -415,30 +415,37 @@ reconf['FORGED_GENERIC_RECEIVED3'] =      'Received=/^\\s*(.+\\n)*by \\d{1,3}\\.\\d{1
 reconf['FORGED_GENERIC_RECEIVED4'] =   'Received=/^\\s*(.+\\n)*from localhost by \\S+;\\s+\\w{3}, \\d+ \\w{3} 20\\d\\d \\d\\d\\:\\d\\d\\:\\d\\d [+-]\\d\\d\\d0[\\s\\r\\n]*$/X'
 
 reconf['FORGED_GENERIC_RECEIVED5'] = function (task)
-       local regexp_text = 'Received:\\s*from \\[(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})\\].*\\n(.+\\n)*Received:\\s*from \\1 by \\S+;\\s+\\w{3}, \\d+ \\w{3} 20\\d\\d \\d\\d\\:\\d\\d\\:\\d\\d [+-]\\d\\d\\d0\\n'
+       local regexp_text = '^\\s*from \\[(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})\\].*\\n(.+\\n)*\\s*from \\1 by \\S+;\\s+\\w{3}, \\d+ \\w{3} 20\\d\\d \\d\\d\\:\\d\\d\\:\\d\\d [+-]\\d\\d\\d0$'
        local re = regexp.get_cached(regexp_text)
        if not re then re = regexp.create(regexp_text, 'i') end
-       local res = re:match(task:get_raw_headers())
-       if res then
-                return true
-        else
-                return false
-        end
+       local headers_recv = task:get_raw_header('Received')
+       if headers_recv then
+               for _,header_r in ipairs(headers_recv) do
+                       if re:match(header_r['value']) then
+                               return true
+                       end
+               end
+       end
+    return false
 end
 
 reconf['INVALID_POSTFIX_RECEIVED'] =   'Received=/ \\(Postfix\\) with ESMTP id [A-Z\\d]+([\\s\\r\\n]+for <\\S+?>)?;[\\s\\r\\n]*[A-Z][a-z]{2}, \\d{1,2} [A-Z][a-z]{2} \\d\\d\\d\\d \\d\\d:\\d\\d:\\d\\d [\\+\\-]\\d\\d\\d\\d$/X'
 
 reconf['INVALID_EXIM_RECEIVED'] = function (task)
        local headers_to = task:get_message():get_header('To')
-       if headers_to then
-               local raw_headers = task:get_raw_headers()
+       if headers_to and table.maxn(headers_to) < 5 then
+               local headers_recv = task:get_raw_header('Received')
                local regexp_text = '^[^\\n]*?<?\\S+?\\@(\\S+)>?\\|.*from \\d+\\.\\d+\\.\\d+\\.\\d+ \\(HELO \\S+\\)[\\s\\r\\n]*by \\1 with esmtp \\(\\S*?[\\?\\@\\(\\)\\s\\.\\+\\*\'\'\\/\\\\,]\\S*\\)[\\s\\r\\n]+id \\S*?[\\)\\(<>\\/\\\\,\\-:=]'
                local re = regexp.get_cached(regexp_text)
                if not re then re = regexp.create(regexp_text, 's') end
-               for _,header_to in ipairs(headers_to) do
-                       if re:match(header_to.."|"..raw_headers) then
+               if headers_recv then
+                       for _,header_to in ipairs(headers_to) do
+                               for _,header_r in ipairs(headers_recv) do
+                                       if re:match(header_to.."|"..header_r['value']) then
                                return true
-                       end
+                               end
+                               end
+                       end
                end
        end
        return false
@@ -446,15 +453,19 @@ end
 
 reconf['INVALID_EXIM_RECEIVED2'] = function (task)
        local headers_to = task:get_message():get_header('To')
-       if headers_to then
-               local raw_headers = task:get_raw_headers()
+       if headers_to and table.maxn(headers_to) < 5 then
+               local headers_recv = task:get_raw_header('Received')
                local regexp_text = '^[^\\n]*?<?\\S+?\\@(\\S+)>?\\|.*from \\d+\\.\\d+\\.\\d+\\.\\d+ \\(HELO \\S+\\)[\\s\\r\\n]*by \\1 with esmtp \\([A-Z]{9,12} [A-Z]{5,6}\\)[\\s\\r\\n]+id [a-zA-Z\\d]{6}-[a-zA-Z\\d]{6}-[a-zA-Z\\d]{2}[\\s\\r\\n]+'
                local re = regexp.get_cached(regexp_text)
                if not re then re = regexp.create(regexp_text, 's') end
-               for _,header_to in ipairs(headers_to) do
-                       if re:match(header_to.."|"..raw_headers) then
+               if headers_recv then
+                       for _,header_to in ipairs(headers_to) do
+                               for _,header_r in ipairs(headers_recv) do
+                                       if re:match(header_to.."|"..header_r['value']) then
                                return true
-                       end
+                               end
+                               end
+                       end
                end
        end
        return false
index 0358a69604556ce23dabea7e778d0ba50a78d995..0c1934345d5c7b006367f47a146843bb7b131c7a 100644 (file)
@@ -98,8 +98,12 @@ static void
 lua_redis_push_error (const gchar *err, struct lua_redis_userdata *ud, gboolean connected)
 {
        struct worker_task                                      **ptask;
+       gboolean                                                          need_unlock = FALSE;
 
-       g_mutex_lock (lua_mtx);
+       /* Avoid LOR here as mutex can be acquired before in lua_call */
+       if (g_mutex_trylock (lua_mtx)) {
+               need_unlock = TRUE;
+       }
        /* Push error */
        lua_rawgeti (ud->L, LUA_REGISTRYINDEX, ud->cbref);
        ptask = lua_newuserdata (ud->L, sizeof (struct worker_task *));
@@ -113,7 +117,9 @@ lua_redis_push_error (const gchar *err, struct lua_redis_userdata *ud, gboolean
        if (lua_pcall (ud->L, 3, 0, 0) != 0) {
                msg_info ("call to callback failed: %s", lua_tostring (ud->L, -1));
        }
-       g_mutex_unlock (lua_mtx);
+       if (need_unlock) {
+               g_mutex_unlock (lua_mtx);
+       }
 
        if (connected) {
                remove_normal_event (ud->task->s, lua_redis_fin, ud);
@@ -130,8 +136,11 @@ static void
 lua_redis_push_data (const redisReply *r, struct lua_redis_userdata *ud)
 {
        struct worker_task                                      **ptask;
+       gboolean                                                          need_unlock = FALSE;
 
-       g_mutex_lock (lua_mtx);
+       if (g_mutex_trylock (lua_mtx)) {
+               need_unlock = TRUE;
+       }
        /* Push error */
        lua_rawgeti (ud->L, LUA_REGISTRYINDEX, ud->cbref);
        ptask = lua_newuserdata (ud->L, sizeof (struct worker_task *));
@@ -161,7 +170,9 @@ lua_redis_push_data (const redisReply *r, struct lua_redis_userdata *ud)
        if (lua_pcall (ud->L, 3, 0, 0) != 0) {
                msg_info ("call to callback failed: %s", lua_tostring (ud->L, -1));
        }
-       g_mutex_unlock (lua_mtx);
+       if (need_unlock) {
+               g_mutex_unlock (lua_mtx);
+       }
 
        remove_normal_event (ud->task->s, lua_redis_fin, ud);
 }
index 5695b6f42e563d3c4a172b754a5734f065208cb4..e44b873c0250ae9f40e83613f79cf766236c5246 100644 (file)
@@ -69,6 +69,7 @@ LUA_FUNCTION_DEF (task, get_helo);
 LUA_FUNCTION_DEF (task, get_images);
 LUA_FUNCTION_DEF (task, get_symbol);
 LUA_FUNCTION_DEF (task, get_date);
+LUA_FUNCTION_DEF (task, get_message_id);
 LUA_FUNCTION_DEF (task, get_timeval);
 LUA_FUNCTION_DEF (task, get_metric_score);
 LUA_FUNCTION_DEF (task, get_metric_action);
@@ -101,6 +102,7 @@ static const struct luaL_reg    tasklib_m[] = {
        LUA_INTERFACE_DEF (task, get_images),
        LUA_INTERFACE_DEF (task, get_symbol),
        LUA_INTERFACE_DEF (task, get_date),
+       LUA_INTERFACE_DEF (task, get_message_id),
        LUA_INTERFACE_DEF (task, get_timeval),
        LUA_INTERFACE_DEF (task, get_metric_score),
        LUA_INTERFACE_DEF (task, get_metric_action),
@@ -1193,6 +1195,21 @@ lua_task_get_date (lua_State *L)
        return 1;
 }
 
+static gint
+lua_task_get_message_id (lua_State *L)
+{
+       struct worker_task             *task = lua_check_task (L);
+
+       if (task != NULL && task->message_id != NULL) {
+               lua_pushstring (L, task->message_id);
+       }
+       else {
+               lua_pushnil (L);
+       }
+
+       return 1;
+}
+
 static gint
 lua_task_get_timeval (lua_State *L)
 {
index 7b1ea3eff6e36443bc0c1a7bc6f48aaf9bf1640d..39bac553426fa5d4174440869969b597e91346aa 100644 (file)
@@ -21,6 +21,7 @@ local bounce_senders = {'postmaster', 'mailer-daemon', '', 'null', 'fetchmail-da
 -- Do not check ratelimits for these senders
 local whitelisted_rcpts = {'postmaster', 'mailer-daemon'}
 local whitelisted_ip = nil
+local max_rcpt = 5
 local upstreams = nil
 
 --- Parse atime and bucket of limit
@@ -167,6 +168,11 @@ local function rate_test_set(task, func)
                rcpts = task:get_recipients_headers()
        end
        if rcpts then
+               if table.maxn(rcpts) > max_rcpt then
+                       rspamd_logger.info(string.format('message <%s> contains %d recipients, maximum is %d',
+                               task:get_message_id(), table.maxn(rcpts), max_rcpt))
+                       return
+               end
                for i,r in ipairs(rcpts) do
                        rcpts_user[i] = get_local_part(r['addr'])
                end
@@ -276,6 +282,7 @@ if rspamd_config:get_api_version() >= 9 then
        rspamd_config:register_module_option('ratelimit', 'whitelisted_rcpts', 'string')
        rspamd_config:register_module_option('ratelimit', 'whitelisted_ip', 'map')
        rspamd_config:register_module_option('ratelimit', 'limit', 'string')
+       rspamd_config:register_module_option('ratelimit', 'max_rcpt', 'uint')
 end
 
 local function parse_whitelisted_rcpts(str)
@@ -301,6 +308,10 @@ if opts then
                whitelisted_ip = rspamd_config:add_hash_map (opts['whitelisted_ip'])
        end
        
+       if opts['max_rcpt'] then
+               max_rcpt = tonumber (opts['max_rcpt'])
+       end
+       
        if not opts['servers'] then
                rspamd_logger.err('no servers are specified')
        else
@@ -312,4 +323,4 @@ if opts then
                        rspamd_config:register_post_filter(rate_set)
                end
        end
-end
+end
\ No newline at end of file