Ivan Stakhov 3 weeks ago
parent
commit
fdd5a957a1
No account linked to committer's email address

+ 157
- 1
src/plugins/lua/known_senders.lua View File

@@ -52,7 +52,17 @@ local settings = {
use_bloom = false,
symbol = 'KNOWN_SENDER',
symbol_unknown = 'UNKNOWN_SENDER',
symbol_check_mail_global = 'INC_MAIL_KNOWN_GLOBALLY',
symbol_check_mail_local = 'INC_MAIL_KNOWN_LOCALLY',
max_recipients = 15,
redis_key = 'rs_known_senders',
sender_prefix = 'rsrk',
sender_key_global = 'verified_senders',
sender_key_size = 20,
reply_sender_privacy = false,
reply_sender_privacy_alg = 'blake2',
reply_sender_privacy_prefix = 'obf',
reply_sender_privacy_length = 16,
}

local settings_schema = lua_redis.enrich_schema({
@@ -72,6 +82,43 @@ local function make_key(input)
return hash:hex()
end

local function make_key_replies(goop, sz, prefix)
local h = rspamd_cryptobox_hash.create()
h:update(goop)
local key = (prefix or '') .. h:base32():sub(1, sz)
return key
end

local zscore_script_id

local function configure_scripts(_, _, _)
-- script checks if given recipients are in the local replies set of the sender
local redis_zscore_script = [[
local replies_recipients_addrs = ARGV

if replies_recipients_addrs ~= nil then
for _, rcpt in ipairs(replies_recipients_addrs) do
local score = redis.call('ZSCORE', KEYS[1], rcpt)
-- check if score is nil (for some reason redis script does not see if score is a nil value)
if type(score) == 'boolean' then
score = nil
-- 0 is stand for failure code
return 0
end
end
-- first number in return statement is stands for the success/failure code
-- where success code is 1 and failure code is 0
return 1
else
-- 0 is a failure code
return 0
end
]]
local zscore_script = lua_util.jinja_template(redis_zscore_script, { })
rspamd_logger.debugm(N, rspamd_config, 'added check for recipients in local replies set script %s', zscore_script)
zscore_script_id = lua_redis.add_redis_script(zscore_script, redis_params)
end

local function check_redis_key(task, key, key_ty)
lua_util.debugm(N, task, 'check key %s, type: %s', key, key_ty)
local function redis_zset_callback(err, data)
@@ -197,6 +244,94 @@ local function known_senders_callback(task)
end
end

local function verify_local_replies_set(task)
local replies_sender = task:get_reply_sender()
if not replies_sender then
lua_util.debugm(N, task, 'Could not get sender')
return nil
end

local replies_recipients = task:get_recipients('mime')

local replies_sender_string = lua_util.maybe_obfuscate_string(tostring(replies_sender), settings, settings.sender_prefix)
local replies_sender_key = make_key_replies(replies_sender_string:lower(), 8)

local function redis_zscore_script_cb(err, data)
if err ~= nil then
rspamd_logger.errx(task, 'Could not verify %s local replies set %s', replies_sender_key, err)
end
rspamd_logger.infox(task, 'DATA: %s', data)
if data ~= 1 then
rspamd_logger.infox(task, 'Recipients were not verified')
return
end
rspamd_logger.infox(task, 'Recipients were verified')
task:insert_result(settings.symbol_check_mail_local, 1.0, replies_sender_key)
end

local replies_recipients_addrs = {}

-- assigning addresses of recipients for params and limiting number of recipients to be checked
for i, rcpt in ipairs(replies_recipients) do
if i > settings['max_recipients'] then
break
end
table.insert(replies_recipients_addrs, rcpt.addr)
end

lua_util.debugm(N, task, 'Making redis request to local replies set')
lua_redis.exec_redis_script(zscore_script_id,
{task = task, is_write = true},
redis_zscore_script_cb,
{ replies_sender_key },
replies_recipients_addrs )
end

local function check_known_incoming_mail_callback(task)
local replies_sender = task:get_reply_sender()
if not replies_sender then
lua_util.debugm(N, task, 'Could not get sender')
return nil
end

-- making sender key
lua_util.debugm(N, task, 'Sender: %s', replies_sender)

local replies_sender_string = lua_util.maybe_obfuscate_string(tostring(replies_sender), settings, settings.sender_prefix)
local replies_sender_key = make_key_replies(replies_sender_string:lower(), 8)

lua_util.debugm(N, task, 'Sender key: %s', replies_sender_key)

local function redis_zscore_global_cb(err, data)
if err ~= nil then
rspamd_logger.errx(task, 'Couldn\'t find sender %s in global replies set. Ended with error: %s', replies_sender, err)
return
end

--checking if zcore have not found score of a sender
if data ~= nil and data ~= '' and type(data) ~= 'userdata' then
rspamd_logger.infox(task, 'Sender: %s verified. Output: %s', replies_sender, data)
task:insert_result(settings.symbol_check_mail_global, 1.0, replies_sender)
else
rspamd_logger.infox(task, 'Sender: %s was not verified', replies_sender)
end
end

-- key for global replies set
local replies_global_key = make_key_replies(settings.sender_key_global, settings.sender_key_size, settings.sender_prefix)

-- using zscore to find sender in global set
lua_util.debugm(N, task, 'Making redis request to global replies set')
lua_redis.redis_make_request(task,
redis_params, -- connect params
replies_sender_key, -- hash key
false, -- is write
redis_zscore_global_cb, --callback
'ZSCORE', -- command
{ replies_global_key, replies_sender } -- arguments
)
end

local opts = rspamd_config:get_all_opt('known_senders')
if opts then
settings = lua_util.override_defaults(settings, opts)
@@ -210,7 +345,8 @@ if opts then

if redis_params then
local map_conf = settings.domains
settings.domains = lua_maps.map_add_from_ucl(settings.domains, 'set', 'domains to track senders from')
settings.domains = lua_maps.map_add_from_ucl(settings.domains,
'set', 'domains to track senders from')
if not settings.domains then
rspamd_logger.errx(rspamd_config, "couldn't add map %s, disable module",
map_conf)
@@ -221,6 +357,8 @@ if opts then
'Known elements redis key', {
type = 'zset/bloom filter',
})
lua_redis.register_prefix(settings.sender_prefix, N,
'Prefix to identify replies sets')
local id = rspamd_config:register_symbol({
name = settings.symbol,
type = 'normal',
@@ -230,6 +368,20 @@ if opts then
augmentations = { string.format("timeout=%f", redis_params.timeout or 0.0) }
})

rspamd_config:register_symbol({
name = settings.symbol_check_mail_local,
type = 'normal',
callback = verify_local_replies_set,
score = 1.0
})

rspamd_config:register_symbol({
name = settings.symbol_check_mail_global,
type = 'normal',
callback = check_known_incoming_mail_callback,
score = 1.0
})

if settings.symbol_unknown and #settings.symbol_unknown > 0 then
rspamd_config:register_symbol({
name = settings.symbol_unknown,
@@ -243,3 +395,7 @@ if opts then
lua_util.disable_module(N, "redis")
end
end

rspamd_config:add_post_init(function(cfg, ev_base, worker)
configure_scripts(cfg, ev_base, worker)
end)

+ 149
- 15
src/plugins/lua/replies.lua View File

@@ -34,9 +34,14 @@ local settings = {
expire = 86400, -- 1 day by default
key_prefix = 'rr',
key_size = 20,
sender_prefix = 'rsrk',
sender_key_global = 'verified_senders',
sender_key_size = 20,
message = 'Message is reply to one we originated',
symbol = 'REPLY',
score = -4, -- Default score
max_local_size = 20,
max_global_size = 30,
use_auth = true,
use_local = true,
cookie = nil,
@@ -44,6 +49,10 @@ local settings = {
cookie_is_pattern = false,
cookie_valid_time = '2w', -- 2 weeks by default
min_message_id = 2, -- minimum length of the message-id header
reply_sender_privacy = false,
reply_sender_privacy_alg = 'blake2',
reply_sender_privacy_prefix = 'obf',
reply_sender_privacy_length = 16,
}

local N = "replies"
@@ -51,25 +60,81 @@ local N = "replies"
local function make_key(goop, sz, prefix)
local h = hash.create()
h:update(goop)
local key
if sz then
key = h:base32():sub(1, sz)
else
key = h:base32()
end
local key = (prefix or '') .. h:base32():sub(1, sz)
return key
end

if prefix then
key = prefix .. key
end
local global_replies_set_script
local local_replies_set_script

local function configure_redis_scripts(_, _)
local redis_script_zadd_global = [[
-- getting last score of the recipients from the global replies set
local data = {}
data = redis.call('ZRANGE', KEYS[1], '-1', '-1', 'WITHSCORES')
local score = tonumber(data[#data])
if score == nil then
score = 0
end

return key
local global_size = redis.call('ZCARD', KEYS[1])
if global_size > {= mgs =} then
redis.call('ZREMRANGEBYRANK', KEYS[1], '{= mgs =}', global_size)
end

local recipients_addrs = ARGV
if recipients_addrs ~= nil then
local params = {}
--table.insert(params, KEYS[1])
for _, rcpt in ipairs(recipients_addrs) do
-- score is using here as a one-based numeration just like in lua
score = score + 1
table.insert(params, score)
table.insert(params, tostring(rcpt))
end
-- adding recipients to the global replies set
redis.call('ZADD', KEYS[1], unpack(params))
end
]]
local set_script_zadd_global = lua_util.jinja_template(redis_script_zadd_global,
{ mgs = settings['max_global_size'] })
rspamd_logger.debugm(N, rspamd_config, 'added add to global replies set script %s', set_script_zadd_global)
global_replies_set_script = lua_redis.add_redis_script(set_script_zadd_global, redis_params)

local redis_script_zadd_local = [[
local local_replies_set_size = redis.call('ZCARD', KEYS[1])
if local_replies_set_size > {= mls =} then
redis.call('ZREMRANGEBYRANK', KEYS[1], '{= mls =}', local_replies_set_size)
end
local given_params = ARGV
if given_params ~= nil then
local task_time = given_params[1]
table.remove(given_params, 1)
-- passing_params is a table that will be passed to the redis
local passing_params = {}
for _, rcpt in ipairs(given_params) do
-- adding recipients for the local replies set
table.insert(passing_params, task_time)
table.insert(passing_params, rcpt)
end
redis.call('ZADD', KEYS[1], unpack(passing_params))

-- setting expire for local replies set
redis.call('EXPIRE', KEYS[1], tostring(math.floor('{= exp =}')))
end
]]
local set_script_zadd_local = lua_util.jinja_template(redis_script_zadd_local,
{ exp = settings['expire'], mls = settings['max_local_size'] })
rspamd_logger.debugm(N, rspamd_config, 'added add to local replies set script %s', set_script_zadd_local)
local_replies_set_script = lua_redis.add_redis_script(set_script_zadd_local, redis_params)
end

local function replies_check(task)
local in_reply_to

local function check_recipient(stored_rcpt)
local rcpts = task:get_recipients('mime')

lua_util.debugm(N, task, 'recipients: %s', rcpts)
if rcpts then
local filter_predicate = function(input_rcpt)
local real_rcpt_h = make_key(input_rcpt:lower(), 8)
@@ -81,7 +146,12 @@ local function replies_check(task)
return rcpt.addr or ''
end, rcpts)) then
lua_util.debugm(N, task, 'reply to %s validated', in_reply_to)
return true

--storing only addr of rcpt
for i = 1, #rcpts do
rcpts[i] = rcpts[i].addr
end
return rcpts
end

rspamd_logger.infox(task, 'ignoring reply to %s as no recipients are matching hash %s',
@@ -91,7 +161,63 @@ local function replies_check(task)
in_reply_to, stored_rcpt)
end

return false
return nil
end

local function add_to_global_replies_set(recipients)
local global_key = make_key(settings.sender_key_global, settings.sender_key_size, settings.sender_prefix)

lua_util.debugm(N, task, 'Adding recipients %s to global replies set', recipients)

local function zadd_global_set_cb(err, _)
if err ~= nil then
rspamd_logger.errx(task, 'failed to add recipients %s to global replies set with error: %s', recipients, err)
return
end
lua_util.debugm(N, task, 'added recipients %s to global replies set', recipients)
end

lua_redis.exec_redis_script(global_replies_set_script,
{ task = task, is_write = true },
zadd_global_set_cb,
{ global_key }, recipients)
end

local function add_to_replies_set(recipients)
local sender = task:get_reply_sender()

local task_time = task:get_timeval(true)

-- making params out of recipients list for replies set
local task_time_str = tostring(task_time)

local sender_string = lua_util.maybe_obfuscate_string(tostring(sender), settings, settings.sender_prefix)
local sender_key = make_key(sender_string:lower(), 8)

local params = recipients

lua_util.debugm(N, task,
'Adding recipients %s to sender %s local replies set', recipients, sender_key)

local function zadd_cb(err, _)
if err ~= nil then
rspamd_logger.errx(task, 'adding to %s failed with error: %s', sender_key, err)
return
end

lua_util.debugm(N, task, 'added data: %s to sender: %s', recipients, sender_key)

table.remove(params, 1)

add_to_global_replies_set(params)
end

table.insert(params, 1, task_time_str)

lua_redis.exec_redis_script(local_replies_set_script,
{ task = task, is_write = true },
zadd_cb,
{ sender_key }, params)
end

local function redis_get_cb(err, data, addr)
@@ -99,8 +225,10 @@ local function replies_check(task)
rspamd_logger.errx(task, 'redis_get_cb error when reading data from %s: %s', addr:get_addr(), err)
return
end
if data and type(data) == 'string' and check_recipient(data) then
local recipients = check_recipient(data)
if type(data) == 'string' and recipients then
-- Hash was found
add_to_replies_set(recipients)
task:insert_result(settings['symbol'], 1.0)
if settings['action'] ~= nil then
local ip_addr = task:get_ip()
@@ -225,7 +353,6 @@ local function replies_check_cookie(task)
if irt == nil then
return
end

local cr = require "rspamd_cryptobox"
-- Extract user part if needed
local extracted_cookie = irt:match('^%<?([^@]+)@.*$')
@@ -285,6 +412,9 @@ if opts then
settings.cookie_valid_time = lua_util.parse_time_interval(settings.cookie_valid_time)
end

lua_redis.register_prefix(settings.sender_prefix, N,
'Prefix to identify replies sets')

local id = rspamd_config:register_symbol({
name = 'REPLIES_CHECK',
type = 'prefilter',
@@ -326,3 +456,7 @@ if opts then
})
end
end

rspamd_config:add_on_load(function(cfg, ev_base, _)
configure_redis_scripts(cfg, ev_base)
end)

+ 39
- 0
test/functional/cases/400_known_senders.robot View File

@@ -7,6 +7,9 @@ Variables ${RSPAMD_TESTDIR}/lib/vars.py

*** Variables ***
${CONFIG} ${RSPAMD_TESTDIR}/configs/known_senders.conf
${SETTINGS_REPLIES} {symbols_enabled = [REPLIES_CHECK, REPLIES_SET, REPLY]}
${SYMBOL_GLOBAL} INC_MAIL_KNOWN_GLOBALLY
${SYMBOL_LOCAL} INC_MAIL_KNOWN_LOCALLY
${REDIS_SCOPE} Suite
${RSPAMD_SCOPE} Suite

@@ -34,3 +37,39 @@ UNKNOWN SENDER WRONG DOMAIN RESCAN
... Settings={symbols_enabled [KNOWN_SENDER]}
Do Not Expect Symbol KNOWN_SENDER
Do Not Expect Symbol UNKNOWN_SENDER

INCOMING MAIL SENDER IS UNKNOWN
Scan File ${RSPAMD_TESTDIR}/messages/inc_mail_unknown_sender.eml
... Settings={symbols_enabled [${SYMBOL_GLOBAL}, ${SYMBOL_LOCAL}]}
Do Not Expect Symbol ${SYMBOL_GLOBAL}
Do Not Expect Symbol ${SYMBOL_LOCAL}
INCOMING MAIL SENDER IS KNOWN RECIPIENTS ARE UNKNOWN
Scan File ${RSPAMD_TESTDIR}/messages/set_replyto_1_1.eml
... IP=8.8.8.8 User=user@emailbl.com
... Settings=${SETTINGS_REPLIES}
Scan File ${RSPAMD_TESTDIR}/messages/replyto_1_1.eml
... IP=8.8.8.8 User=user@emailbl.com
... Settings=${SETTINGS_REPLIES}
Scan File ${RSPAMD_TESTDIR}/messages/inc_mail_known_sender.eml
... IP=8.8.8.8 User=user@emailbl.com
... Settings={symbols_enabled [${SYMBOL_GLOBAL}, ${SYMBOL_LOCAL}]}
Expect Symbol ${SYMBOL_GLOBAL}
Do Not Expect Symbol ${SYMBOL_LOCAL}

INCOMING MAIL SENDER IS KNOWN RECIPIENTS ARE KNOWN
Scan File ${RSPAMD_TESTDIR}/messages/set_replyto_1_1.eml
... IP=8.8.8.8 User=user@emailbl.com
... Settings=${SETTINGS_REPLIES}
Scan File ${RSPAMD_TESTDIR}/messages/replyto_1_1.eml
... IP=8.8.8.8 User=user@emailbl.com
... Settings=${SETTINGS_REPLIES}
Scan File ${RSPAMD_TESTDIR}/messages/inc_mail_known_sender.eml
... IP=8.8.8.8 User=user@emailbl.com
... Settings=${SETTINGS_REPLIES}
Scan File ${RSPAMD_TESTDIR}/messages/inc_mail_known_sender.eml
... IP=8.8.8.8 User=user@emailbl.com
... Settings={symbols_enabled [${SYMBOL_GLOBAL}, ${SYMBOL_LOCAL}]}
Expect Symbol ${SYMBOL_GLOBAL}
Expect Symbol ${SYMBOL_LOCAL}


+ 48
- 0
test/functional/cases/410_replies.robot View File

@@ -0,0 +1,48 @@
*** Settings ***
Suite Setup Rspamd Redis Setup
Suite Teardown Rspamd Redis Teardown
Library ${RSPAMD_TESTDIR}/lib/rspamd.py
Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
Variables ${RSPAMD_TESTDIR}/lib/vars.py

*** Variables ***
${CONFIG} ${RSPAMD_TESTDIR}/configs/replies.conf
${SETTINGS_REPLIES} {symbols_enabled = [REPLIES_CHECK, REPLIES_SET, REPLY]}
${SYMBOL} REPLY
${REDIS_SCOPE} Suite
${RSPAMD_SCOPE} Suite

*** Test Cases ***
Reply to 1 sender 1 recipients
Scan File ${RSPAMD_TESTDIR}/messages/set_replyto_1_1.eml
... IP=8.8.8.8 User=user@emailbl.com
... Settings=${SETTINGS_REPLIES}
Scan File ${RSPAMD_TESTDIR}/messages/replyto_1_1.eml
... IP=8.8.8.8 User=user@emailbl.com
... Settings=${SETTINGS_REPLIES}
Expect Symbol ${SYMBOL}
Reply to 1 sender 2 recipients first is set second is not
Scan File ${RSPAMD_TESTDIR}/messages/set_replyto_1_2_first.eml
... IP=8.8.8.8 User=user@emailbl.com
... Settings=${SETTINGS_REPLIES}
Scan File ${RSPAMD_TESTDIR}/messages/replyto_1_2.eml
... IP=8.8.8.8 User=user@emailbl.com
... Settings=${SETTINGS_REPLIES}
Expect Symbol ${SYMBOL}

Reply to 1 sender 2 recipients 1 rcpt is same
Scan File ${RSPAMD_TESTDIR}/messages/replyto_1_2_s.eml
... IP=8.8.8.8 User=user@emailbl.com
... Settings=${SETTINGS_REPLIES}
Expect Symbol ${SYMBOL}

Reply to another sender 2 recipients
Scan File ${RSPAMD_TESTDIR}/messages/set_replyto_2_2.eml
... IP=8.8.8.8 User=another@emailbl.com
... Settings=${SETTINGS_REPLIES}
Scan File ${RSPAMD_TESTDIR}/messages/replyto_2_2.eml
... IP=8.8.8.8 User=another@emailbl.com
... Settings=${SETTINGS_REPLIES}
Expect Symbol ${SYMBOL}


+ 1
- 0
test/functional/configs/merged.conf View File

@@ -35,5 +35,6 @@ lua = "{= env.TESTDIR =}/lua/magic.lua"
# 380_external_relay
lua = "{= env.TESTDIR =}/lua/external_relay.lua"


.include(priority=1,duplicate=merge) "{= env.TESTDIR =}/configs/merged-local.conf"
.include(priority=2,duplicate=replace) "{= env.TESTDIR =}/configs/merged-override.conf"

+ 6
- 0
test/functional/configs/replies.conf View File

@@ -0,0 +1,6 @@
.include "{= env.TESTDIR =}/../../conf/rspamd.conf"

lua = "{= env.TESTDIR =}/lua/test_coverage.lua"

.include(priority=1,duplicate=merge) "{= env.TESTDIR =}/configs/merged-local.conf"
.include(priority=2,duplicate=replace) "{= env.TESTDIR =}/configs/merged-override.conf"

+ 11
- 0
test/functional/messages/inc_mail_known_sender.eml View File

@@ -0,0 +1,11 @@
in-reply-to: 00020
message-id: 000200
From: <xxx@abrakadabra.com>
To: <llll@abrakadabra.com>, <user@emailbl.com>
Subject: 06.07.2015
Date: Mon, 6 Jul 2015 10:35:56 +0200
MIME-Version: 1.0
Content-Type: text/plain;
charset="Windows-1251"

test

+ 11
- 0
test/functional/messages/inc_mail_unknown_sender.eml View File

@@ -0,0 +1,11 @@
in-reply-to: 00020
message-id: 000200
From: <ffff@abrakadabra.com>
To: <zzzz@abrakadabra.com>
Subject: 06.07.2015
Date: Mon, 6 Jul 2015 10:35:56 +0200
MIME-Version: 1.0
Content-Type: text/plain;
charset="Windows-1251"

test

+ 11
- 0
test/functional/messages/replyto_1_1.eml View File

@@ -0,0 +1,11 @@
in-reply-to: 0002
message-id: 00020
From: <user@emailbl.com>
To: <xxx@abrakadabra.com>
Subject: 06.07.2015
Date: Mon, 6 Jul 2015 10:35:56 +0200
MIME-Version: 1.0
Content-Type: text/plain;
charset="Windows-1251"

test

+ 11
- 0
test/functional/messages/replyto_1_2.eml View File

@@ -0,0 +1,11 @@
in-reply-to: 0012
message-id: 00120
From: <user@emailbl.com>
To: <xxxx@emailbl.com>, <yyyy@example.com>
Subject: 06.07.2015
Date: Mon, 6 Jul 2015 10:35:56 +0200
MIME-Version: 1.0
Content-Type: text/plain;
charset="Windows-1251"

test

+ 11
- 0
test/functional/messages/replyto_1_2_s.eml View File

@@ -0,0 +1,11 @@
in-reply-to: 0012
message-id: 00121
From: <user@emailbl.com>
To: <xxxx@emailbl.com>, <llll@example.com>
Subject: 06.07.2015
Date: Mon, 6 Jul 2015 10:35:56 +0200
MIME-Version: 1.0
Content-Type: text/plain;
charset="Windows-1251"

test

+ 11
- 0
test/functional/messages/replyto_2_2.eml View File

@@ -0,0 +1,11 @@
in-reply-to: 0022
message-id: 00220
From: <another@emailbl.com>
To: <xxxx@emailbl.com>, <yyyy@example.com>
Subject: 06.07.2015
Date: Mon, 6 Jul 2015 10:35:56 +0200
MIME-Version: 1.0
Content-Type: text/plain;
charset="Windows-1251"

test

+ 12
- 0
test/functional/messages/set_replyto_1_1.eml View File

@@ -0,0 +1,12 @@
in-reply-to: 0002
message-id: 0002
From: <xxx@abrakadabra.com>
To: <user@emailbl.com>
Subject: 06.07.2015
Date: Mon, 6 Jul 2015 10:35:56 +0200
MIME-Version: 1.0
Content-Type: text/plain;
charset="Windows-1251"

test


+ 11
- 0
test/functional/messages/set_replyto_1_2_first.eml View File

@@ -0,0 +1,11 @@
in-reply-to: 0012
message-id: 0012
From: <xxxx@emailbl.com>
To: <user@emailbl.com>
Subject: 06.07.2015
Date: Mon, 6 Jul 2015 10:35:56 +0200
MIME-Version: 1.0
Content-Type: text/plain;
charset="Windows-1251"

test

+ 11
- 0
test/functional/messages/set_replyto_2_2.eml View File

@@ -0,0 +1,11 @@
in-reply-to: 0022
message-id: 0022
From: <xxxx@emailbl.com>
To: <another@emailbl.com>
Subject: 06.07.2015
Date: Mon, 6 Jul 2015 10:35:56 +0200
MIME-Version: 1.0
Content-Type: text/plain;
charset="Windows-1251"

test

Loading…
Cancel
Save