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 23KB

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