You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

configwizard.lua 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811
  1. --[[
  2. Copyright (c) 2018, Vsevolod Stakhov <vsevolod@highsecure.ru>
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. ]]--
  13. local ansicolors = require "ansicolors"
  14. local local_conf = rspamd_paths['CONFDIR']
  15. local rspamd_util = require "rspamd_util"
  16. local rspamd_logger = require "rspamd_logger"
  17. local lua_util = require "lua_util"
  18. local lua_stat_tools = require "lua_stat"
  19. local lua_redis = require "lua_redis"
  20. local ucl = require "ucl"
  21. local argparse = require "argparse"
  22. local fun = require "fun"
  23. local plugins_stat = require "plugins_stats"
  24. local rspamd_logo = [[
  25. ____ _
  26. | _ \ ___ _ __ __ _ _ __ ___ __| |
  27. | |_) |/ __|| '_ \ / _` || '_ ` _ \ / _` |
  28. | _ < \__ \| |_) || (_| || | | | | || (_| |
  29. |_| \_\|___/| .__/ \__,_||_| |_| |_| \__,_|
  30. |_|
  31. ]]
  32. local parser = argparse()
  33. :name "rspamadm configwizard"
  34. :description "Perform guided configuration for Rspamd daemon"
  35. :help_description_margin(32)
  36. parser:option "-c --config"
  37. :description "Path to config file"
  38. :argname("<file>")
  39. :default(rspamd_paths["CONFDIR"] .. "/" .. "rspamd.conf")
  40. parser:argument "checks"
  41. :description "Checks to do (or 'list')"
  42. :argname("<checks>")
  43. :args "*"
  44. local redis_params
  45. local function printf(fmt, ...)
  46. if fmt then
  47. io.write(string.format(fmt, ...))
  48. end
  49. io.write('\n')
  50. end
  51. local function highlight(str)
  52. return ansicolors.white .. str .. ansicolors.reset
  53. end
  54. local function ask_yes_no(greet, default)
  55. local def_str
  56. if default then
  57. greet = greet .. "[Y/n]: "
  58. def_str = "yes"
  59. else
  60. greet = greet .. "[y/N]: "
  61. def_str = "no"
  62. end
  63. local reply = rspamd_util.readline(greet)
  64. if not reply then os.exit(0) end
  65. if #reply == 0 then reply = def_str end
  66. reply = reply:lower()
  67. if reply == 'y' or reply == 'yes' then return true end
  68. return false
  69. end
  70. local function readline_default(greet, def_value)
  71. local reply = rspamd_util.readline(greet)
  72. if not reply then os.exit(0) end
  73. if #reply == 0 then return def_value end
  74. return reply
  75. end
  76. local function readline_expire()
  77. local expire = '100d'
  78. repeat
  79. expire = readline_default("Expire time for new tokens [" .. expire .. "]: ",
  80. expire)
  81. expire = lua_util.parse_time_interval(expire)
  82. if not expire then
  83. expire = '100d'
  84. elseif expire > 2147483647 then
  85. printf("The maximum possible value is 2147483647 (about 68y)")
  86. expire = '68y'
  87. elseif expire < -1 then
  88. printf("The value must be a non-negative integer or -1")
  89. expire = -1
  90. elseif expire ~= math.floor(expire) then
  91. printf("The value must be an integer")
  92. expire = math.floor(expire)
  93. else
  94. return expire
  95. end
  96. until false
  97. end
  98. local function print_changes(changes)
  99. local function print_change(k, c, where)
  100. printf('File: %s, changes list:', highlight(local_conf .. '/'
  101. .. where .. '/'.. k))
  102. for ek,ev in pairs(c) do
  103. printf("%s => %s", highlight(ek), rspamd_logger.slog("%s", ev))
  104. end
  105. end
  106. for k, v in pairs(changes.l) do
  107. print_change(k, v, 'local.d')
  108. if changes.o[k] then
  109. v = changes.o[k]
  110. print_change(k, v, 'override.d')
  111. end
  112. print()
  113. end
  114. end
  115. local function apply_changes(changes)
  116. local function dirname(fname)
  117. if fname:match(".-/.-") then
  118. return string.gsub(fname, "(.*/)(.*)", "%1")
  119. else
  120. return nil
  121. end
  122. end
  123. local function apply_change(k, c, where)
  124. local fname = local_conf .. '/' .. where .. '/'.. k
  125. if not rspamd_util.file_exists(fname) then
  126. printf("Create file %s", highlight(fname))
  127. local dname = dirname(fname)
  128. if dname then
  129. local ret, err = rspamd_util.mkdir(dname, true)
  130. if not ret then
  131. printf("Cannot make directory %s: %s", dname, highlight(err))
  132. os.exit(1)
  133. end
  134. end
  135. end
  136. local f = io.open(fname, "a+")
  137. if not f then
  138. printf("Cannot open file %s, aborting", highlight(fname))
  139. os.exit(1)
  140. end
  141. f:write(ucl.to_config(c))
  142. f:close()
  143. end
  144. for k, v in pairs(changes.l) do
  145. apply_change(k, v, 'local.d')
  146. if changes.o[k] then
  147. v = changes.o[k]
  148. apply_change(k, v, 'override.d')
  149. end
  150. end
  151. end
  152. local function setup_controller(controller, changes)
  153. printf("Setup %s and controller worker:", highlight("WebUI"))
  154. if not controller.password or controller.password == 'q1' then
  155. if ask_yes_no("Controller password is not set, do you want to set one?", true) then
  156. local pw_encrypted = rspamadm.pw_encrypt()
  157. if pw_encrypted then
  158. printf("Set encrypted password to: %s", highlight(pw_encrypted))
  159. changes.l['worker-controller.inc'] = {
  160. password = pw_encrypted
  161. }
  162. end
  163. end
  164. end
  165. end
  166. local function setup_redis(cfg, changes)
  167. local function parse_servers(servers)
  168. local ls = lua_util.rspamd_str_split(servers, ",")
  169. return ls
  170. end
  171. printf("%s servers are not set:", highlight("Redis"))
  172. printf("The following modules will be enabled if you add Redis servers:")
  173. for k,_ in pairs(rspamd_plugins_state.disabled_redis) do
  174. printf("\t* %s", highlight(k))
  175. end
  176. if ask_yes_no("Do you wish to set Redis servers?", true) then
  177. local read_servers = readline_default("Input read only servers separated by `,` [default: localhost]: ",
  178. "localhost")
  179. local rs = parse_servers(read_servers)
  180. if rs and #rs > 0 then
  181. changes.l['redis.conf'] = {
  182. read_servers = table.concat(rs, ",")
  183. }
  184. end
  185. local write_servers = readline_default("Input write only servers separated by `,` [default: "
  186. .. read_servers .. "]: ", read_servers)
  187. if not write_servers or #write_servers == 0 then
  188. printf("Use read servers %s as write servers", highlight(table.concat(rs, ",")))
  189. write_servers = read_servers
  190. end
  191. redis_params = {
  192. read_servers = rs,
  193. }
  194. local ws = parse_servers(write_servers)
  195. if ws and #ws > 0 then
  196. changes.l['redis.conf']['write_servers'] = table.concat(ws, ",")
  197. redis_params['write_servers'] = ws
  198. end
  199. if ask_yes_no('Do you have any password set for your Redis?') then
  200. local passwd = readline_default("Enter Redis password:", nil)
  201. if passwd then
  202. changes.l['redis.conf']['password'] = passwd
  203. redis_params['password'] = passwd
  204. end
  205. end
  206. if ask_yes_no('Do you have any specific database for your Redis?') then
  207. local db = readline_default("Enter Redis database:", nil)
  208. if db then
  209. changes.l['redis.conf']['db'] = db
  210. redis_params['db'] = db
  211. end
  212. end
  213. end
  214. end
  215. local function setup_dkim_signing(cfg, changes)
  216. -- Remove the trailing slash of a pathname, if present.
  217. local function remove_trailing_slash(path)
  218. if string.sub(path, -1) ~= "/" then return path end
  219. return string.sub(path, 1, string.len(path) - 1)
  220. end
  221. printf('How would you like to set up DKIM signing?')
  222. printf('1. Use domain from %s for sign', highlight('mime from header'))
  223. printf('2. Use domain from %s for sign', highlight('SMTP envelope from'))
  224. printf('3. Use domain from %s for sign', highlight('authenticated user'))
  225. printf('4. Sign all mail from %s', highlight('specific networks'))
  226. printf()
  227. local sign_type = readline_default('Enter your choice (1, 2, 3, 4) [default: 1]: ', '1')
  228. local sign_networks
  229. local allow_mismatch
  230. local sign_authenticated
  231. local use_esld
  232. local sign_domain = 'pet luacheck'
  233. local defined_auth_types = {'header', 'envelope', 'auth', 'recipient'}
  234. if sign_type == '4' then
  235. repeat
  236. sign_networks = readline_default('Enter list of networks to perform dkim signing: ',
  237. '')
  238. until #sign_networks ~= 0
  239. sign_networks = fun.totable(fun.map(lua_util.rspamd_str_trim,
  240. lua_util.str_split(sign_networks, ',; ')))
  241. printf('What domain would you like to use for signing?')
  242. printf('* %s to use mime from domain', highlight('header'))
  243. printf('* %s to use SMTP from domain', highlight('envelope'))
  244. printf('* %s to use domain from SMTP auth', highlight('auth'))
  245. printf('* %s to use domain from SMTP recipient', highlight('recipient'))
  246. printf('* anything else to use as a %s domain (e.g. `example.com`)', highlight('static'))
  247. printf()
  248. sign_domain = readline_default('Enter your choice [default: header]: ', 'header')
  249. else
  250. if sign_type == '1' then
  251. sign_domain = 'header'
  252. elseif sign_type == '2' then
  253. sign_domain = 'envelope'
  254. else
  255. sign_domain = 'auth'
  256. end
  257. end
  258. if sign_type ~= '3' then
  259. sign_authenticated = ask_yes_no(
  260. string.format('Do you want to sign mail from %s? ',
  261. highlight('authenticated users')), true)
  262. else
  263. sign_authenticated = true
  264. end
  265. if fun.any(function(s) return s == sign_domain end, defined_auth_types) then
  266. -- Allow mismatch
  267. allow_mismatch = ask_yes_no(
  268. string.format('Allow data %s, e.g. if mime from domain is not equal to authenticated user domain? ',
  269. highlight('mismatch')), true)
  270. -- ESLD check
  271. use_esld = ask_yes_no(
  272. string.format('Do you want to use %s domain (e.g. example.com instead of foo.example.com)? ',
  273. highlight('effective')), true)
  274. else
  275. allow_mismatch = true
  276. end
  277. local domains = {}
  278. local has_domains = false
  279. local dkim_keys_dir = rspamd_paths["DBDIR"] .. "/dkim/"
  280. local prompt = string.format("Enter output directory for the keys [default: %s]: ",
  281. highlight(dkim_keys_dir))
  282. dkim_keys_dir = remove_trailing_slash(readline_default(prompt, dkim_keys_dir))
  283. local ret, err = rspamd_util.mkdir(dkim_keys_dir, true)
  284. if not ret then
  285. printf("Cannot make directory %s: %s", dkim_keys_dir, highlight(err))
  286. os.exit(1)
  287. end
  288. local function print_domains()
  289. printf("Domains configured:")
  290. for k,v in pairs(domains) do
  291. printf("Domain: %s, selector: %s, privkey: %s", highlight(k),
  292. v.selector, v.privkey)
  293. end
  294. printf("--")
  295. end
  296. repeat
  297. if has_domains then
  298. print_domains()
  299. end
  300. local domain
  301. repeat
  302. domain = rspamd_util.readline("Enter domain to sign: ")
  303. if not domain then
  304. os.exit(1)
  305. end
  306. until #domain ~= 0
  307. local selector = readline_default("Enter selector [default: dkim]: ", 'dkim')
  308. if not selector then selector = 'dkim' end
  309. local privkey_file = string.format("%s/%s.%s.key", dkim_keys_dir, domain,
  310. selector)
  311. if not rspamd_util.file_exists(privkey_file) then
  312. if ask_yes_no("Do you want to create privkey " .. highlight(privkey_file),
  313. true) then
  314. local pubkey_file = privkey_file .. ".pub"
  315. rspamadm.dkim_keygen(domain, selector, privkey_file, pubkey_file, 2048)
  316. local f = io.open(pubkey_file)
  317. if not f then
  318. printf("Cannot open pubkey file %s, fatal error", highlight(pubkey_file))
  319. os.exit(1)
  320. end
  321. local content = f:read("*all")
  322. f:close()
  323. print("To make dkim signing working, you need to place the following record in your DNS zone:")
  324. print(content)
  325. end
  326. end
  327. domains[domain] = {
  328. selector = selector,
  329. path = privkey_file,
  330. }
  331. until not ask_yes_no("Do you wish to add another DKIM domain?")
  332. changes.l['dkim_signing.conf'] = {domain = domains}
  333. local res_tbl = changes.l['dkim_signing.conf']
  334. if sign_networks then
  335. res_tbl.sign_networks = sign_networks
  336. res_tbl.use_domain_sign_networks = sign_domain
  337. else
  338. res_tbl.use_domain = sign_domain
  339. end
  340. if allow_mismatch then
  341. res_tbl.allow_hdrfrom_mismatch = true
  342. res_tbl.allow_hdrfrom_mismatch_sign_networks = true
  343. res_tbl.allow_username_mismatch = true
  344. end
  345. res_tbl.use_esld = use_esld
  346. res_tbl.sign_authenticated = sign_authenticated
  347. end
  348. local function check_redis_classifier(cls, changes)
  349. local symbol_spam, symbol_ham
  350. -- Load symbols from statfiles
  351. local statfiles = cls.statfile
  352. for _,stf in ipairs(statfiles) do
  353. local symbol = stf.symbol or 'undefined'
  354. local spam
  355. if stf.spam then
  356. spam = stf.spam
  357. else
  358. if string.match(symbol:upper(), 'SPAM') then
  359. spam = true
  360. else
  361. spam = false
  362. end
  363. end
  364. if spam then
  365. symbol_spam = symbol
  366. else
  367. symbol_ham = symbol
  368. end
  369. end
  370. if not symbol_spam or not symbol_ham then
  371. printf("Classifier has no symbols defined")
  372. return
  373. end
  374. local parsed_redis = lua_redis.try_load_redis_servers(cls, nil)
  375. if not parsed_redis and redis_params then
  376. parsed_redis = lua_redis.try_load_redis_servers(redis_params, nil)
  377. if not parsed_redis then
  378. printf("Cannot parse Redis params")
  379. return
  380. end
  381. end
  382. local function try_convert(update_config)
  383. if ask_yes_no("Do you wish to convert data to the new schema?", true) then
  384. local expire = readline_expire()
  385. if not lua_stat_tools.convert_bayes_schema(parsed_redis, symbol_spam,
  386. symbol_ham, expire) then
  387. printf("Conversion failed")
  388. else
  389. printf("Conversion succeed")
  390. if update_config then
  391. changes.l['classifier-bayes.conf'] = {
  392. new_schema = true,
  393. }
  394. if expire then
  395. changes.l['classifier-bayes.conf'].expire = expire
  396. end
  397. end
  398. end
  399. end
  400. end
  401. local function get_version(conn)
  402. conn:add_cmd("SMEMBERS", {"RS_keys"})
  403. local ret,members = conn:exec()
  404. -- Empty db
  405. if not ret or #members == 0 then return false,0 end
  406. -- We still need to check versions
  407. local lua_script = [[
  408. local ver = 0
  409. local tst = redis.call('GET', KEYS[1]..'_version')
  410. if tst then
  411. ver = tonumber(tst) or 0
  412. end
  413. return ver
  414. ]]
  415. conn:add_cmd('EVAL', {lua_script, '1', 'RS'})
  416. local _,ver = conn:exec()
  417. return true,tonumber(ver)
  418. end
  419. local function check_expire(conn)
  420. -- We still need to check versions
  421. local lua_script = [[
  422. local ttl = 0
  423. local sc = redis.call('SCAN', 0, 'MATCH', 'RS*_*', 'COUNT', 1)
  424. local _,key = sc[1], sc[2]
  425. if key and key[1] then
  426. ttl = redis.call('TTL', key[1])
  427. end
  428. return ttl
  429. ]]
  430. conn:add_cmd('EVAL', {lua_script, '0'})
  431. local _,ttl = conn:exec()
  432. return tonumber(ttl)
  433. end
  434. local res,conn = lua_redis.redis_connect_sync(parsed_redis, true)
  435. if not res then
  436. printf("Cannot connect to Redis server")
  437. return false
  438. end
  439. if not cls.new_schema then
  440. local r,ver = get_version(conn)
  441. if not r then return false end
  442. if ver ~= 2 then
  443. if not ver then
  444. printf('Key "RS_version" has not been found in Redis for %s/%s',
  445. symbol_ham, symbol_spam)
  446. else
  447. printf("You are using an old schema version: %s for %s/%s",
  448. ver, symbol_ham, symbol_spam)
  449. end
  450. try_convert(true)
  451. else
  452. printf("You have configured an old schema for %s/%s but your data has new layout",
  453. symbol_ham, symbol_spam)
  454. if ask_yes_no("Switch config to the new schema?", true) then
  455. changes.l['classifier-bayes.conf'] = {
  456. new_schema = true,
  457. }
  458. local expire = check_expire(conn)
  459. if expire then
  460. changes.l['classifier-bayes.conf'].expire = expire
  461. end
  462. end
  463. end
  464. else
  465. local r,ver = get_version(conn)
  466. if not r then return false end
  467. if ver ~= 2 then
  468. printf("You have configured new schema for %s/%s but your DB has old version: %s",
  469. symbol_spam, symbol_ham, ver)
  470. try_convert(false)
  471. else
  472. printf(
  473. 'You have configured new schema for %s/%s and your DB already has new layout (v. %s).' ..
  474. ' DB conversion is not needed.',
  475. symbol_spam, symbol_ham, ver)
  476. end
  477. end
  478. end
  479. local function setup_statistic(cfg, changes)
  480. local sqlite_configs = lua_stat_tools.load_sqlite_config(cfg)
  481. if #sqlite_configs > 0 then
  482. if not redis_params then
  483. printf('You have %d sqlite classifiers, but you have no Redis servers being set',
  484. #sqlite_configs)
  485. return false
  486. end
  487. local parsed_redis = lua_redis.try_load_redis_servers(redis_params, nil)
  488. if parsed_redis then
  489. printf('You have %d sqlite classifiers', #sqlite_configs)
  490. local expire = readline_expire()
  491. local reset_previous = ask_yes_no("Reset previous data?")
  492. if ask_yes_no('Do you wish to convert them to Redis?', true) then
  493. for _,cls in ipairs(sqlite_configs) do
  494. if rspamd_util.file_exists(cls.db_spam) and rspamd_util.file_exists(cls.db_ham) then
  495. if not lua_stat_tools.convert_sqlite_to_redis(parsed_redis, cls.db_spam,
  496. cls.db_ham, cls.symbol_spam, cls.symbol_ham, cls.learn_cache, expire,
  497. reset_previous) then
  498. rspamd_logger.errx('conversion failed')
  499. return false
  500. end
  501. else
  502. rspamd_logger.messagex('cannot find %s and %s, skip conversion',
  503. cls.db_spam, cls.db_ham)
  504. end
  505. rspamd_logger.messagex('Converted classifier to the from sqlite to redis')
  506. changes.l['classifier-bayes.conf'] = {
  507. backend = 'redis',
  508. new_schema = true,
  509. }
  510. if expire then
  511. changes.l['classifier-bayes.conf'].expire = expire
  512. end
  513. if cls.learn_cache then
  514. changes.l['classifier-bayes.conf'].cache = {
  515. backend = 'redis'
  516. }
  517. end
  518. end
  519. end
  520. end
  521. else
  522. -- Check sanity for the existing Redis classifiers
  523. local classifier = cfg.classifier
  524. if classifier then
  525. if classifier[1] then
  526. for _,cls in ipairs(classifier) do
  527. if cls.bayes then cls = cls.bayes end
  528. if cls.backend and cls.backend == 'redis' then
  529. check_redis_classifier(cls, changes)
  530. end
  531. end
  532. else
  533. if classifier.bayes then
  534. classifier = classifier.bayes
  535. if classifier[1] then
  536. for _,cls in ipairs(classifier) do
  537. if cls.backend and cls.backend == 'redis' then
  538. check_redis_classifier(cls, changes)
  539. end
  540. end
  541. else
  542. if classifier.backend and classifier.backend == 'redis' then
  543. check_redis_classifier(classifier, changes)
  544. end
  545. end
  546. end
  547. end
  548. end
  549. end
  550. end
  551. local function find_worker(cfg, wtype)
  552. if cfg.worker then
  553. for k,s in pairs(cfg.worker) do
  554. if type(k) == 'number' and type(s) == 'table' then
  555. if s[wtype] then return s[wtype] end
  556. end
  557. if type(s) == 'table' and s.type and s.type == wtype then
  558. return s
  559. end
  560. if type(k) == 'string' and k == wtype then return s end
  561. end
  562. end
  563. return nil
  564. end
  565. return {
  566. handler = function(cmd_args)
  567. local changes = {
  568. l = {}, -- local changes
  569. o = {}, -- override changes
  570. }
  571. local interactive_start = true
  572. local checks = {}
  573. local all_checks = {
  574. 'controller',
  575. 'redis',
  576. 'dkim',
  577. 'statistic',
  578. }
  579. local opts = parser:parse(cmd_args)
  580. local args = opts['checks'] or {}
  581. local _r,err = rspamd_config:load_ucl(opts['config'])
  582. if not _r then
  583. rspamd_logger.errx('cannot parse %s: %s', opts['config'], err)
  584. os.exit(1)
  585. end
  586. _r,err = rspamd_config:parse_rcl({'logging', 'worker'})
  587. if not _r then
  588. rspamd_logger.errx('cannot process %s: %s', opts['config'], err)
  589. os.exit(1)
  590. end
  591. local cfg = rspamd_config:get_ucl()
  592. if not rspamd_config:init_modules() then
  593. rspamd_logger.errx('cannot init modules when parsing %s', opts['config'])
  594. os.exit(1)
  595. end
  596. if #args > 0 then
  597. interactive_start = false
  598. for _,arg in ipairs(args) do
  599. if arg == 'all' then
  600. checks = all_checks
  601. elseif arg == 'list' then
  602. printf(highlight(rspamd_logo))
  603. printf('Available modules')
  604. for _,c in ipairs(all_checks) do
  605. printf('- %s', c)
  606. end
  607. return
  608. else
  609. table.insert(checks, arg)
  610. end
  611. end
  612. else
  613. checks = all_checks
  614. end
  615. local function has_check(check)
  616. for _,c in ipairs(checks) do
  617. if c == check then
  618. return true
  619. end
  620. end
  621. return false
  622. end
  623. rspamd_util.umask('022')
  624. if interactive_start then
  625. printf(highlight(rspamd_logo))
  626. printf("Welcome to the configuration tool")
  627. printf("We use %s configuration file, writing results to %s",
  628. highlight(opts['config']), highlight(local_conf))
  629. plugins_stat(nil, nil)
  630. end
  631. if not interactive_start or
  632. ask_yes_no("Do you wish to continue?", true) then
  633. if has_check('controller') then
  634. local controller = find_worker(cfg, 'controller')
  635. if controller then
  636. setup_controller(controller, changes)
  637. end
  638. end
  639. if has_check('redis') then
  640. if not cfg.redis or (not cfg.redis.servers and not cfg.redis.read_servers) then
  641. setup_redis(cfg, changes)
  642. else
  643. redis_params = cfg.redis
  644. end
  645. else
  646. redis_params = cfg.redis
  647. end
  648. if has_check('dkim') then
  649. if cfg.dkim_signing and not cfg.dkim_signing.domain then
  650. if ask_yes_no('Do you want to setup dkim signing feature?') then
  651. setup_dkim_signing(cfg, changes)
  652. end
  653. end
  654. end
  655. if has_check('statistic') or has_check('statistics') then
  656. setup_statistic(cfg, changes)
  657. end
  658. local nchanges = 0
  659. for _,_ in pairs(changes.l) do nchanges = nchanges + 1 end
  660. for _,_ in pairs(changes.o) do nchanges = nchanges + 1 end
  661. if nchanges > 0 then
  662. print_changes(changes)
  663. if ask_yes_no("Apply changes?", true) then
  664. apply_changes(changes)
  665. printf("%d changes applied, the wizard is finished now", nchanges)
  666. printf("*** Please reload the Rspamd configuration ***")
  667. else
  668. printf("No changes applied, the wizard is finished now")
  669. end
  670. else
  671. printf("No changes found, the wizard is finished now")
  672. end
  673. end
  674. end,
  675. name = 'configwizard',
  676. description = parser._description,
  677. }