aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/lua/fuzzy_collect.lua
blob: 9a63823d9351e2ae50e06c99804ce2f54b6a9cda (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
--[[
Copyright (c) 2022, Vsevolod Stakhov <vsevolod@rspamd.com>

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
]] --

if confighelp then
  return
end

local rspamd_logger = require "rspamd_logger"
local rspamd_util = require "rspamd_util"
local rspamd_http = require "rspamd_http"
local rspamd_keypairlib = require "rspamd_cryptobox_keypair"
local rspamd_cryptolib = require "rspamd_cryptobox"
local fun = require "fun"

local settings = {
  sync_time = 60.0,
  saved_cookie = '',
  timeout = 10.0,
}

local function send_data_mirror(m, cfg, ev_base, body)
  local function store_callback(err, _, _, _)
    if err then
      rspamd_logger.errx(cfg, 'cannot save data on %(%s): %s', m.server, m.name, err)
    else
      rspamd_logger.infox(cfg, 'saved data on %s(%s)', m.server, m.name)
    end
  end
  rspamd_http.request{
    url = string.format('http://%s//update_v1/%s', m.server, m.name),
    resolver = cfg:get_resolver(),
    config = cfg,
    ev_base = ev_base,
    timeout = settings.timeout,
    callback = store_callback,
    body = body,
    peer_key = m.pubkey,
    keypair = m.keypair,
  }
end

local function collect_fuzzy_hashes(cfg, ev_base)
  local function data_callback(err, _, body, _)
    if not body or err then
      rspamd_logger.errx(cfg, 'cannot load data: %s', err)
    else
      -- Here, we actually copy body once for each mirror
      fun.each(function(_, v) send_data_mirror(v, cfg, ev_base, body) end,
        settings.mirrors)
    end
  end

  local function cookie_callback(err, _, body, _)
    if not body or err then
      rspamd_logger.errx(cfg, 'cannot load cookie: %s', err)
    else
      if settings.saved_cookie ~= tostring(body) then
        settings.saved_cookie = tostring(body)
        rspamd_logger.infox(cfg, 'received collection cookie %s',
          tostring(rspamd_util.encode_base32(settings.saved_cookie:sub(1, 6))))
        local sig = rspamd_cryptolib.sign_memory(settings.sign_keypair,
          settings.saved_cookie)
        if not sig then
          rspamd_logger.info(cfg, 'cannot sign cookie')
        else
          rspamd_http.request{
            url = string.format('http://%s/data', settings.collect_server),
            resolver = cfg:get_resolver(),
            config = cfg,
            ev_base = ev_base,
            timeout = settings.timeout,
            callback = data_callback,
            peer_key = settings.collect_pubkey,
            headers = {
              Signature = sig:hex()
            },
            opaque_body = true,
          }
        end
      else
        rspamd_logger.info(cfg, 'cookie has not changed, do not update')
      end
    end
  end
  rspamd_logger.infox(cfg, 'start fuzzy collection, next sync in %s seconds',
    settings.sync_time)
  rspamd_http.request{
    url = string.format('http://%s/cookie', settings.collect_server),
    resolver = cfg:get_resolver(),
    config = cfg,
    ev_base = ev_base,
    timeout = settings.timeout,
    callback = cookie_callback,
    peer_key = settings.collect_pubkey,
  }

  return settings.sync_time
end

local function test_mirror_config(k, m)
  if not m.server then
    rspamd_logger.errx(rspamd_config, 'server is missing for the mirror')
    return false
  end

  if not m.pubkey then
    rspamd_logger.errx(rspamd_config, 'pubkey is missing for the mirror')
    return false
  end

  if type(k) ~= 'string' and not m.name then
    rspamd_logger.errx(rspamd_config, 'name is missing for the mirror')
    return false
  end

  if not m.keypair then
    rspamd_logger.errx(rspamd_config, 'keypair is missing for the mirror')
    return false
  end

  if not m.name then
    m.name = k
  end

  return true
end

local opts = rspamd_config:get_all_opt('fuzzy_collect')

if opts and type(opts) == 'table' then
  for k,v in pairs(opts) do
    settings[k] = v
  end
  local sane_config = true

  if not settings['sign_keypair'] then
    rspamd_logger.errx(rspamd_config, 'sign_keypair is missing')
    sane_config = false
  end

  settings['sign_keypair'] = rspamd_keypairlib.create(settings['sign_keypair'])
  if not settings['sign_keypair'] then
    rspamd_logger.errx(rspamd_config, 'sign_keypair is invalid')
    sane_config = false
  end

  if not settings['collect_server'] then
    rspamd_logger.errx(rspamd_config, 'collect_server is missing')
    sane_config = false
  end

  if not settings['collect_pubkey'] then
    rspamd_logger.errx(rspamd_config, 'collect_pubkey is missing')
    sane_config = false
  end

  if not settings['mirrors'] then
    rspamd_logger.errx(rspamd_config, 'collect_pubkey is missing')
    sane_config = false
  end

  if not fun.all(test_mirror_config, settings['mirrors']) then
    sane_config = false
  end

  if sane_config then
    rspamd_config:add_on_load(function(_, ev_base, worker)
      if worker:is_primary_controller() then
        rspamd_config:add_periodic(ev_base, 0.0,
          function(_cfg, _ev_base)
            return collect_fuzzy_hashes(_cfg, _ev_base)
          end)
      end
    end)
  else
    rspamd_logger.errx(rspamd_config, 'module is not configured properly')
  end
end