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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849
  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['LOCAL_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 username set for your Redis (ACL SETUSER and Redis 6.0+)') then
  210. local username = readline_default("Enter Redis username:", nil)
  211. if username then
  212. changes.l['redis.conf'].username = username
  213. redis_params.username = username
  214. end
  215. local passwd = readline_default("Enter Redis password:", nil)
  216. if passwd then
  217. changes.l['redis.conf']['password'] = passwd
  218. redis_params['password'] = passwd
  219. end
  220. elseif ask_yes_no('Do you have any password set for your Redis?') then
  221. local passwd = readline_default("Enter Redis password:", nil)
  222. if passwd then
  223. changes.l['redis.conf']['password'] = passwd
  224. redis_params['password'] = passwd
  225. end
  226. end
  227. if ask_yes_no('Do you have any specific database for your Redis?') then
  228. local db = readline_default("Enter Redis database:", nil)
  229. if db then
  230. changes.l['redis.conf']['db'] = db
  231. redis_params['db'] = db
  232. end
  233. end
  234. end
  235. end
  236. local function setup_dkim_signing(cfg, changes)
  237. -- Remove the trailing slash of a pathname, if present.
  238. local function remove_trailing_slash(path)
  239. if string.sub(path, -1) ~= "/" then
  240. return path
  241. end
  242. return string.sub(path, 1, string.len(path) - 1)
  243. end
  244. printf('How would you like to set up DKIM signing?')
  245. printf('1. Use domain from %s for sign', highlight('mime from header'))
  246. printf('2. Use domain from %s for sign', highlight('SMTP envelope from'))
  247. printf('3. Use domain from %s for sign', highlight('authenticated user'))
  248. printf('4. Sign all mail from %s', highlight('specific networks'))
  249. printf()
  250. local sign_type = readline_default('Enter your choice (1, 2, 3, 4) [default: 1]: ', '1')
  251. local sign_networks
  252. local allow_mismatch
  253. local sign_authenticated
  254. local use_esld
  255. local sign_domain = 'pet luacheck'
  256. local defined_auth_types = { 'header', 'envelope', 'auth', 'recipient' }
  257. if sign_type == '4' then
  258. repeat
  259. sign_networks = readline_default('Enter list of networks to perform dkim signing: ',
  260. '')
  261. until #sign_networks ~= 0
  262. sign_networks = fun.totable(fun.map(lua_util.rspamd_str_trim,
  263. lua_util.str_split(sign_networks, ',; ')))
  264. printf('What domain would you like to use for signing?')
  265. printf('* %s to use mime from domain', highlight('header'))
  266. printf('* %s to use SMTP from domain', highlight('envelope'))
  267. printf('* %s to use domain from SMTP auth', highlight('auth'))
  268. printf('* %s to use domain from SMTP recipient', highlight('recipient'))
  269. printf('* anything else to use as a %s domain (e.g. `example.com`)', highlight('static'))
  270. printf()
  271. sign_domain = readline_default('Enter your choice [default: header]: ', 'header')
  272. else
  273. if sign_type == '1' then
  274. sign_domain = 'header'
  275. elseif sign_type == '2' then
  276. sign_domain = 'envelope'
  277. else
  278. sign_domain = 'auth'
  279. end
  280. end
  281. if sign_type ~= '3' then
  282. sign_authenticated = ask_yes_no(
  283. string.format('Do you want to sign mail from %s? ',
  284. highlight('authenticated users')), true)
  285. else
  286. sign_authenticated = true
  287. end
  288. if fun.any(function(s)
  289. return s == sign_domain
  290. end, defined_auth_types) then
  291. -- Allow mismatch
  292. allow_mismatch = ask_yes_no(
  293. string.format('Allow data %s, e.g. if mime from domain is not equal to authenticated user domain? ',
  294. highlight('mismatch')), true)
  295. -- ESLD check
  296. use_esld = ask_yes_no(
  297. string.format('Do you want to use %s domain (e.g. example.com instead of foo.example.com)? ',
  298. highlight('effective')), true)
  299. else
  300. allow_mismatch = true
  301. end
  302. local domains = {}
  303. local has_domains = false
  304. local dkim_keys_dir = rspamd_paths["DBDIR"] .. "/dkim/"
  305. local prompt = string.format("Enter output directory for the keys [default: %s]: ",
  306. highlight(dkim_keys_dir))
  307. dkim_keys_dir = remove_trailing_slash(readline_default(prompt, dkim_keys_dir))
  308. local ret, err = rspamd_util.mkdir(dkim_keys_dir, true)
  309. if not ret then
  310. printf("Cannot make directory %s: %s", dkim_keys_dir, highlight(err))
  311. os.exit(1)
  312. end
  313. local function print_domains()
  314. printf("Domains configured:")
  315. for k, v in pairs(domains) do
  316. printf("Domain: %s, selector: %s, privkey: %s", highlight(k),
  317. v.selector, v.privkey)
  318. end
  319. printf("--")
  320. end
  321. local function print_public_key(pk)
  322. local base64_pk = tostring(rspamd_util.encode_base64(pk))
  323. printf('v=DKIM1; k=rsa; p=%s\n', base64_pk)
  324. end
  325. repeat
  326. if has_domains then
  327. print_domains()
  328. end
  329. local domain
  330. repeat
  331. domain = rspamd_util.readline("Enter domain to sign: ")
  332. if not domain then
  333. os.exit(1)
  334. end
  335. until #domain ~= 0
  336. local selector = readline_default("Enter selector [default: dkim]: ", 'dkim')
  337. if not selector then
  338. selector = 'dkim'
  339. end
  340. local privkey_file = string.format("%s/%s.%s.key", dkim_keys_dir, domain,
  341. selector)
  342. if not rspamd_util.file_exists(privkey_file) then
  343. if ask_yes_no("Do you want to create privkey " .. highlight(privkey_file),
  344. true) then
  345. local rsa = require "rspamd_rsa"
  346. local sk, pk = rsa.keypair(2048)
  347. sk:save(privkey_file, 'pem')
  348. print("You need to chown private key file to rspamd user!!")
  349. print("To make dkim signing working, to place the following record in your DNS zone:")
  350. print_public_key(tostring(pk))
  351. end
  352. end
  353. domains[domain] = {
  354. selector = selector,
  355. path = privkey_file,
  356. }
  357. until not ask_yes_no("Do you wish to add another DKIM domain?")
  358. changes.l['dkim_signing.conf'] = { domain = domains }
  359. local res_tbl = changes.l['dkim_signing.conf']
  360. if sign_networks then
  361. res_tbl.sign_networks = sign_networks
  362. res_tbl.use_domain_sign_networks = sign_domain
  363. else
  364. res_tbl.use_domain = sign_domain
  365. end
  366. if allow_mismatch then
  367. res_tbl.allow_hdrfrom_mismatch = true
  368. res_tbl.allow_hdrfrom_mismatch_sign_networks = true
  369. res_tbl.allow_username_mismatch = true
  370. end
  371. res_tbl.use_esld = use_esld
  372. res_tbl.sign_authenticated = sign_authenticated
  373. end
  374. local function check_redis_classifier(cls, changes)
  375. local symbol_spam, symbol_ham
  376. -- Load symbols from statfiles
  377. local statfiles = cls.statfile
  378. for _, stf in ipairs(statfiles) do
  379. local symbol = stf.symbol or 'undefined'
  380. local spam
  381. if stf.spam then
  382. spam = stf.spam
  383. else
  384. if string.match(symbol:upper(), 'SPAM') then
  385. spam = true
  386. else
  387. spam = false
  388. end
  389. end
  390. if spam then
  391. symbol_spam = symbol
  392. else
  393. symbol_ham = symbol
  394. end
  395. end
  396. if not symbol_spam or not symbol_ham then
  397. printf("Classifier has no symbols defined")
  398. return
  399. end
  400. local parsed_redis = lua_redis.try_load_redis_servers(cls, nil)
  401. if not parsed_redis and redis_params then
  402. parsed_redis = lua_redis.try_load_redis_servers(redis_params, nil)
  403. if not parsed_redis then
  404. printf("Cannot parse Redis params")
  405. return
  406. end
  407. end
  408. local function try_convert(update_config)
  409. if ask_yes_no("Do you wish to convert data to the new schema?", true) then
  410. local expire = readline_expire()
  411. if not lua_stat_tools.convert_bayes_schema(parsed_redis, symbol_spam,
  412. symbol_ham, expire) then
  413. printf("Conversion failed")
  414. else
  415. printf("Conversion succeed")
  416. if update_config then
  417. changes.l['classifier-bayes.conf'] = {
  418. new_schema = true,
  419. }
  420. if expire then
  421. changes.l['classifier-bayes.conf'].expire = expire
  422. end
  423. end
  424. end
  425. end
  426. end
  427. local function get_version(conn)
  428. conn:add_cmd("SMEMBERS", { "RS_keys" })
  429. local ret, members = conn:exec()
  430. -- Empty db
  431. if not ret or #members == 0 then
  432. return false, 0
  433. end
  434. -- We still need to check versions
  435. local lua_script = [[
  436. local ver = 0
  437. local tst = redis.call('GET', KEYS[1]..'_version')
  438. if tst then
  439. ver = tonumber(tst) or 0
  440. end
  441. return ver
  442. ]]
  443. conn:add_cmd('EVAL', { lua_script, '1', 'RS' })
  444. local _, ver = conn:exec()
  445. return true, tonumber(ver)
  446. end
  447. local function check_expire(conn)
  448. -- We still need to check versions
  449. local lua_script = [[
  450. local ttl = 0
  451. local sc = redis.call('SCAN', 0, 'MATCH', 'RS*_*', 'COUNT', 1)
  452. local _,key = sc[1], sc[2]
  453. if key and key[1] then
  454. ttl = redis.call('TTL', key[1])
  455. end
  456. return ttl
  457. ]]
  458. conn:add_cmd('EVAL', { lua_script, '0' })
  459. local _, ttl = conn:exec()
  460. return tonumber(ttl)
  461. end
  462. local res, conn = lua_redis.redis_connect_sync(parsed_redis, true)
  463. if not res then
  464. printf("Cannot connect to Redis server")
  465. return false
  466. end
  467. if not cls.new_schema then
  468. local r, ver = get_version(conn)
  469. if not r then
  470. return false
  471. end
  472. if ver ~= 2 then
  473. if not ver then
  474. printf('Key "RS_version" has not been found in Redis for %s/%s',
  475. symbol_ham, symbol_spam)
  476. else
  477. printf("You are using an old schema version: %s for %s/%s",
  478. ver, symbol_ham, symbol_spam)
  479. end
  480. try_convert(true)
  481. else
  482. printf("You have configured an old schema for %s/%s but your data has new layout",
  483. symbol_ham, symbol_spam)
  484. if ask_yes_no("Switch config to the new schema?", true) then
  485. changes.l['classifier-bayes.conf'] = {
  486. new_schema = true,
  487. }
  488. local expire = check_expire(conn)
  489. if expire then
  490. changes.l['classifier-bayes.conf'].expire = expire
  491. end
  492. end
  493. end
  494. else
  495. local r, ver = get_version(conn)
  496. if not r then
  497. return false
  498. end
  499. if ver ~= 2 then
  500. printf("You have configured new schema for %s/%s but your DB has old version: %s",
  501. symbol_spam, symbol_ham, ver)
  502. try_convert(false)
  503. else
  504. printf(
  505. 'You have configured new schema for %s/%s and your DB already has new layout (v. %s).' ..
  506. ' DB conversion is not needed.',
  507. symbol_spam, symbol_ham, ver)
  508. end
  509. end
  510. end
  511. local function setup_statistic(cfg, changes)
  512. local sqlite_configs = lua_stat_tools.load_sqlite_config(cfg)
  513. if #sqlite_configs > 0 then
  514. if not redis_params then
  515. printf('You have %d sqlite classifiers, but you have no Redis servers being set',
  516. #sqlite_configs)
  517. return false
  518. end
  519. local parsed_redis = lua_redis.try_load_redis_servers(redis_params, nil)
  520. if parsed_redis then
  521. printf('You have %d sqlite classifiers', #sqlite_configs)
  522. local expire = readline_expire()
  523. local reset_previous = ask_yes_no("Reset previous data?")
  524. if ask_yes_no('Do you wish to convert them to Redis?', true) then
  525. for _, cls in ipairs(sqlite_configs) do
  526. if rspamd_util.file_exists(cls.db_spam) and rspamd_util.file_exists(cls.db_ham) then
  527. if not lua_stat_tools.convert_sqlite_to_redis(parsed_redis, cls.db_spam,
  528. cls.db_ham, cls.symbol_spam, cls.symbol_ham, cls.learn_cache, expire,
  529. reset_previous) then
  530. rspamd_logger.errx('conversion failed')
  531. return false
  532. end
  533. else
  534. rspamd_logger.messagex('cannot find %s and %s, skip conversion',
  535. cls.db_spam, cls.db_ham)
  536. end
  537. rspamd_logger.messagex('Converted classifier to the from sqlite to redis')
  538. changes.l['classifier-bayes.conf'] = {
  539. backend = 'redis',
  540. new_schema = true,
  541. }
  542. if expire then
  543. changes.l['classifier-bayes.conf'].expire = expire
  544. end
  545. if cls.learn_cache then
  546. changes.l['classifier-bayes.conf'].cache = {
  547. backend = 'redis'
  548. }
  549. end
  550. end
  551. end
  552. end
  553. else
  554. -- Check sanity for the existing Redis classifiers
  555. local classifier = cfg.classifier
  556. if classifier then
  557. if classifier[1] then
  558. for _, cls in ipairs(classifier) do
  559. if cls.bayes then
  560. cls = cls.bayes
  561. end
  562. if cls.backend and cls.backend == 'redis' then
  563. check_redis_classifier(cls, changes)
  564. end
  565. end
  566. else
  567. if classifier.bayes then
  568. classifier = classifier.bayes
  569. if classifier[1] then
  570. for _, cls in ipairs(classifier) do
  571. if cls.backend and cls.backend == 'redis' then
  572. check_redis_classifier(cls, changes)
  573. end
  574. end
  575. else
  576. if classifier.backend and classifier.backend == 'redis' then
  577. check_redis_classifier(classifier, changes)
  578. end
  579. end
  580. end
  581. end
  582. end
  583. end
  584. end
  585. local function find_worker(cfg, wtype)
  586. if cfg.worker then
  587. for k, s in pairs(cfg.worker) do
  588. if type(k) == 'number' and type(s) == 'table' then
  589. if s[wtype] then
  590. return s[wtype]
  591. end
  592. end
  593. if type(s) == 'table' and s.type and s.type == wtype then
  594. return s
  595. end
  596. if type(k) == 'string' and k == wtype then
  597. return s
  598. end
  599. end
  600. end
  601. return nil
  602. end
  603. return {
  604. handler = function(cmd_args)
  605. local changes = {
  606. l = {}, -- local changes
  607. o = {}, -- override changes
  608. }
  609. local interactive_start = true
  610. local checks = {}
  611. local all_checks = {
  612. 'controller',
  613. 'redis',
  614. 'dkim',
  615. 'statistic',
  616. }
  617. local opts = parser:parse(cmd_args)
  618. local args = opts['checks'] or {}
  619. local _r, err = rspamd_config:load_ucl(opts['config'])
  620. if not _r then
  621. rspamd_logger.errx('cannot parse %s: %s', opts['config'], err)
  622. os.exit(1)
  623. end
  624. _r, err = rspamd_config:parse_rcl({ 'logging', 'worker' })
  625. if not _r then
  626. rspamd_logger.errx('cannot process %s: %s', opts['config'], err)
  627. os.exit(1)
  628. end
  629. local cfg = rspamd_config:get_ucl()
  630. if not rspamd_config:init_modules() then
  631. rspamd_logger.errx('cannot init modules when parsing %s', opts['config'])
  632. os.exit(1)
  633. end
  634. if #args > 0 then
  635. interactive_start = false
  636. for _, arg in ipairs(args) do
  637. if arg == 'all' then
  638. checks = all_checks
  639. elseif arg == 'list' then
  640. printf(highlight(rspamd_logo))
  641. printf('Available modules')
  642. for _, c in ipairs(all_checks) do
  643. printf('- %s', c)
  644. end
  645. return
  646. else
  647. table.insert(checks, arg)
  648. end
  649. end
  650. else
  651. checks = all_checks
  652. end
  653. local function has_check(check)
  654. for _, c in ipairs(checks) do
  655. if c == check then
  656. return true
  657. end
  658. end
  659. return false
  660. end
  661. rspamd_util.umask('022')
  662. if interactive_start then
  663. printf(highlight(rspamd_logo))
  664. printf("Welcome to the configuration tool")
  665. printf("We use %s configuration file, writing results to %s",
  666. highlight(opts['config']), highlight(local_conf))
  667. plugins_stat(nil, nil)
  668. end
  669. if not interactive_start or
  670. ask_yes_no("Do you wish to continue?", true) then
  671. if has_check('controller') then
  672. local controller = find_worker(cfg, 'controller')
  673. if controller then
  674. setup_controller(controller, changes)
  675. end
  676. end
  677. if has_check('redis') then
  678. if not cfg.redis or (not cfg.redis.servers and not cfg.redis.read_servers) then
  679. setup_redis(cfg, changes)
  680. else
  681. redis_params = cfg.redis
  682. end
  683. else
  684. redis_params = cfg.redis
  685. end
  686. if has_check('dkim') then
  687. if cfg.dkim_signing and not cfg.dkim_signing.domain then
  688. if ask_yes_no('Do you want to setup dkim signing feature?') then
  689. setup_dkim_signing(cfg, changes)
  690. end
  691. end
  692. end
  693. if has_check('statistic') or has_check('statistics') then
  694. setup_statistic(cfg, changes)
  695. end
  696. local nchanges = 0
  697. for _, _ in pairs(changes.l) do
  698. nchanges = nchanges + 1
  699. end
  700. for _, _ in pairs(changes.o) do
  701. nchanges = nchanges + 1
  702. end
  703. if nchanges > 0 then
  704. print_changes(changes)
  705. if ask_yes_no("Apply changes?", true) then
  706. apply_changes(changes)
  707. printf("%d changes applied, the wizard is finished now", nchanges)
  708. printf("*** Please reload the Rspamd configuration ***")
  709. else
  710. printf("No changes applied, the wizard is finished now")
  711. end
  712. else
  713. printf("No changes found, the wizard is finished now")
  714. end
  715. end
  716. end,
  717. name = 'configwizard',
  718. description = parser._description,
  719. }