Software / code / prosody
Comparison
prosodyctl @ 11200:bf8f2da84007
Merge 0.11->trunk
| author | Kim Alvefur <zash@zash.se> |
|---|---|
| date | Thu, 05 Nov 2020 22:31:25 +0100 |
| parent | 11135:56490ffde93f |
| child | 11247:4e803e80d7b1 |
comparison
equal
deleted
inserted
replaced
| 11199:6c7c50a4de32 | 11200:bf8f2da84007 |
|---|---|
| 8 -- | 8 -- |
| 9 | 9 |
| 10 -- prosodyctl - command-line controller for Prosody XMPP server | 10 -- prosodyctl - command-line controller for Prosody XMPP server |
| 11 | 11 |
| 12 -- Will be modified by configure script if run -- | 12 -- Will be modified by configure script if run -- |
| 13 | |
| 14 CFG_SOURCEDIR=CFG_SOURCEDIR or os.getenv("PROSODY_SRCDIR"); | 13 CFG_SOURCEDIR=CFG_SOURCEDIR or os.getenv("PROSODY_SRCDIR"); |
| 15 CFG_CONFIGDIR=CFG_CONFIGDIR or os.getenv("PROSODY_CFGDIR"); | 14 CFG_CONFIGDIR=CFG_CONFIGDIR or os.getenv("PROSODY_CFGDIR"); |
| 16 CFG_PLUGINDIR=CFG_PLUGINDIR or os.getenv("PROSODY_PLUGINDIR"); | 15 CFG_PLUGINDIR=CFG_PLUGINDIR or os.getenv("PROSODY_PLUGINDIR"); |
| 17 CFG_DATADIR=CFG_DATADIR or os.getenv("PROSODY_DATADIR"); | 16 CFG_DATADIR=CFG_DATADIR or os.getenv("PROSODY_DATADIR"); |
| 18 | 17 |
| 47 | 46 |
| 48 local startup = require "util.startup"; | 47 local startup = require "util.startup"; |
| 49 startup.prosodyctl(); | 48 startup.prosodyctl(); |
| 50 | 49 |
| 51 ----------- | 50 ----------- |
| 52 | |
| 53 local error_messages = setmetatable({ | |
| 54 ["invalid-username"] = "The given username is invalid in a Jabber ID"; | |
| 55 ["invalid-hostname"] = "The given hostname is invalid"; | |
| 56 ["no-password"] = "No password was supplied"; | |
| 57 ["no-such-user"] = "The given user does not exist on the server"; | |
| 58 ["no-such-host"] = "The given hostname does not exist in the config"; | |
| 59 ["unable-to-save-data"] = "Unable to store, perhaps you don't have permission?"; | |
| 60 ["no-pidfile"] = "There is no 'pidfile' option in the configuration file, see https://prosody.im/doc/prosodyctl#pidfile for help"; | |
| 61 ["invalid-pidfile"] = "The 'pidfile' option in the configuration file is not a string, see https://prosody.im/doc/prosodyctl#pidfile for help"; | |
| 62 ["no-posix"] = "The mod_posix module is not enabled in the Prosody config file, see https://prosody.im/doc/prosodyctl for more info"; | |
| 63 ["no-such-method"] = "This module has no commands"; | |
| 64 ["not-running"] = "Prosody is not running"; | |
| 65 }, { __index = function (_,k) return "Error: "..(tostring(k):gsub("%-", " "):gsub("^.", string.upper)); end }); | |
| 66 | 51 |
| 67 local configmanager = require "core.configmanager"; | 52 local configmanager = require "core.configmanager"; |
| 68 local modulemanager = require "core.modulemanager" | 53 local modulemanager = require "core.modulemanager" |
| 69 local prosodyctl = require "util.prosodyctl" | 54 local prosodyctl = require "util.prosodyctl" |
| 70 local socket = require "socket" | 55 local socket = require "socket" |
| 71 local dependencies = require "util.dependencies"; | 56 local dependencies = require "util.dependencies"; |
| 72 local lfs = dependencies.softreq "lfs"; | 57 local lfs = dependencies.softreq "lfs"; |
| 73 | 58 |
| 74 ----------------------- | 59 ----------------------- |
| 75 | 60 |
| 61 local human_io = require "util.human.io"; | |
| 62 | |
| 76 local show_message, show_warning = prosodyctl.show_message, prosodyctl.show_warning; | 63 local show_message, show_warning = prosodyctl.show_message, prosodyctl.show_warning; |
| 77 local show_usage = prosodyctl.show_usage; | 64 local show_usage = prosodyctl.show_usage; |
| 78 local show_yesno = prosodyctl.show_yesno; | 65 local read_password = human_io.read_password; |
| 79 local show_prompt = prosodyctl.show_prompt; | 66 local call_luarocks = prosodyctl.call_luarocks; |
| 80 local read_password = prosodyctl.read_password; | 67 local error_messages = prosodyctl.error_messages; |
| 81 | 68 |
| 82 local jid_split = require "util.jid".prepped_split; | 69 local jid_split = require "util.jid".prepped_split; |
| 83 | 70 |
| 84 local prosodyctl_timeout = (configmanager.get("*", "prosodyctl_timeout") or 5) * 2; | 71 local prosodyctl_timeout = (configmanager.get("*", "prosodyctl_timeout") or 5) * 2; |
| 85 ----------------------- | 72 ----------------------- |
| 86 local commands = {}; | 73 local commands = {}; |
| 87 local command = arg[1]; | 74 local command = table.remove(arg, 1); |
| 75 | |
| 76 function commands.install(arg) | |
| 77 if arg[1] == "--help" then | |
| 78 show_usage([[install]], [[Installs a prosody/luarocks plugin]]); | |
| 79 return 1; | |
| 80 end | |
| 81 -- TODO finalize config option name | |
| 82 call_luarocks("install", arg[1], configmanager.get("*", "plugin_server") or "http://localhost/"); | |
| 83 end | |
| 84 | |
| 85 function commands.remove(arg) | |
| 86 if arg[1] == "--help" then | |
| 87 show_usage([[remove]], [[Removes a module installed in the working directory's plugins folder]]); | |
| 88 return 1; | |
| 89 end | |
| 90 call_luarocks("remove", arg[1]) | |
| 91 end | |
| 92 | |
| 93 function commands.list(arg) | |
| 94 if arg[1] == "--help" then | |
| 95 show_usage([[list]], [[Shows installed rocks]]); | |
| 96 return 1; | |
| 97 end | |
| 98 call_luarocks("list", arg[1]) | |
| 99 end | |
| 88 | 100 |
| 89 function commands.adduser(arg) | 101 function commands.adduser(arg) |
| 90 if not arg[1] or arg[1] == "--help" then | 102 if not arg[1] or arg[1] == "--help" then |
| 91 show_usage([[adduser JID]], [[Create the specified user account in Prosody]]); | 103 show_usage([[adduser JID]], [[Create the specified user account in Prosody]]); |
| 92 return 1; | 104 return 1; |
| 119 | 131 |
| 120 local ok, msg = prosodyctl.adduser { user = user, host = host, password = password }; | 132 local ok, msg = prosodyctl.adduser { user = user, host = host, password = password }; |
| 121 | 133 |
| 122 if ok then return 0; end | 134 if ok then return 0; end |
| 123 | 135 |
| 124 show_message(msg) | 136 show_message(error_messages[msg]) |
| 125 return 1; | 137 return 1; |
| 126 end | 138 end |
| 127 | 139 |
| 128 function commands.passwd(arg) | 140 function commands.passwd(arg) |
| 129 if not arg[1] or arg[1] == "--help" then | 141 if not arg[1] or arg[1] == "--help" then |
| 236 show_message("Prosody is already running with PID %s", ret or "(unknown)"); | 248 show_message("Prosody is already running with PID %s", ret or "(unknown)"); |
| 237 return 1; | 249 return 1; |
| 238 end | 250 end |
| 239 | 251 |
| 240 --luacheck: ignore 411/ret | 252 --luacheck: ignore 411/ret |
| 241 local ok, ret = prosodyctl.start(prosody.paths.source); | 253 local lua; |
| 254 do | |
| 255 local i = 0; | |
| 256 repeat | |
| 257 i = i - 1; | |
| 258 until arg[i-1] == nil | |
| 259 lua = arg[i]; | |
| 260 end | |
| 261 local ok, ret = prosodyctl.start(prosody.paths.source, lua); | |
| 242 if ok then | 262 if ok then |
| 243 local daemonize = configmanager.get("*", "daemonize"); | 263 local daemonize = configmanager.get("*", "daemonize"); |
| 244 if daemonize == nil then | 264 if daemonize == nil then |
| 245 daemonize = prosody.installed; | 265 daemonize = prosody.installed; |
| 246 end | 266 end |
| 357 show_usage([[about]], [[Show information about this Prosody installation]]); | 377 show_usage([[about]], [[Show information about this Prosody installation]]); |
| 358 return 1; | 378 return 1; |
| 359 end | 379 end |
| 360 | 380 |
| 361 local pwd = "."; | 381 local pwd = "."; |
| 362 local array = require "util.array"; | 382 local sorted_pairs = require "util.iterators".sorted_pairs; |
| 363 local keys = require "util.iterators".keys; | |
| 364 local hg = require"util.mercurial"; | 383 local hg = require"util.mercurial"; |
| 365 local relpath = configmanager.resolve_relative_path; | 384 local relpath = configmanager.resolve_relative_path; |
| 366 | 385 |
| 367 print("Prosody "..(prosody.version or "(unknown version)")); | 386 print("Prosody "..(prosody.version or "(unknown version)")); |
| 368 print(""); | 387 print(""); |
| 381 hgrepo = hgrepo == "010452cfaf53" and "prosody-modules"; | 400 hgrepo = hgrepo == "010452cfaf53" and "prosody-modules"; |
| 382 return path..(hgid and " - "..(hgrepo or "HG").." rev: "..hgid or "") | 401 return path..(hgid and " - "..(hgrepo or "HG").." rev: "..hgid or "") |
| 383 .."\n "; | 402 .."\n "; |
| 384 end))); | 403 end))); |
| 385 print(""); | 404 print(""); |
| 405 local have_pposix, pposix = pcall(require, "util.pposix"); | |
| 406 if have_pposix and pposix.uname then | |
| 407 print("# Operating system"); | |
| 408 local uname, err = pposix.uname(); | |
| 409 print(uname and uname.sysname .. " " .. uname.release or "Unknown POSIX", err or ""); | |
| 410 print(""); | |
| 411 end | |
| 386 print("# Lua environment"); | 412 print("# Lua environment"); |
| 387 print("Lua version: ", _G._VERSION); | 413 print("Lua version: ", _G._VERSION); |
| 388 print(""); | 414 print(""); |
| 389 print("Lua module search paths:"); | 415 print("Lua module search paths:"); |
| 390 for path in package.path:gmatch("[^;]+") do | 416 for path in package.path:gmatch("[^;]+") do |
| 411 print(""); | 437 print(""); |
| 412 print("Backend: "..require "net.server".get_backend()); | 438 print("Backend: "..require "net.server".get_backend()); |
| 413 print(""); | 439 print(""); |
| 414 print("# Lua module versions"); | 440 print("# Lua module versions"); |
| 415 local module_versions, longest_name = {}, 8; | 441 local module_versions, longest_name = {}, 8; |
| 416 local luaevent =dependencies.softreq"luaevent"; | 442 local library_versions = {}; |
| 417 dependencies.softreq"ssl"; | 443 dependencies.softreq"ssl"; |
| 418 dependencies.softreq"DBI"; | 444 dependencies.softreq"DBI"; |
| 445 local friendly_names = { | |
| 446 DBI = "LuaDBI"; | |
| 447 lfs = "LuaFileSystem"; | |
| 448 lunbound = "luaunbound"; | |
| 449 lxp = "LuaExpat"; | |
| 450 socket = "LuaSocket"; | |
| 451 ssl = "LuaSec"; | |
| 452 }; | |
| 453 local lunbound = dependencies.softreq"lunbound"; | |
| 419 for name, module in pairs(package.loaded) do | 454 for name, module in pairs(package.loaded) do |
| 420 if type(module) == "table" and rawget(module, "_VERSION") | 455 if type(module) == "table" and rawget(module, "_VERSION") |
| 421 and name ~= "_G" and not name:match("%.") then | 456 and name ~= "_G" and not name:match("%.") then |
| 457 name = friendly_names[name] or name; | |
| 422 if #name > longest_name then | 458 if #name > longest_name then |
| 423 longest_name = #name; | 459 longest_name = #name; |
| 424 end | 460 end |
| 425 module_versions[name] = module._VERSION; | 461 local mod_version = module._VERSION; |
| 426 end | 462 if tostring(mod_version):sub(1, #name+1) == name .. " " then |
| 427 end | 463 mod_version = mod_version:sub(#name+2); |
| 428 if luaevent then | 464 end |
| 429 module_versions["libevent"] = luaevent.core.libevent_version(); | 465 module_versions[name] = mod_version; |
| 430 end | 466 end |
| 431 local sorted_keys = array.collect(keys(module_versions)):sort(); | 467 end |
| 432 for _, name in ipairs(sorted_keys) do | 468 if lunbound then |
| 433 print(name..":"..string.rep(" ", longest_name-#name), module_versions[name]); | 469 if not module_versions["luaunbound"] then |
| 470 module_versions["luaunbound"] = "0.5 (?)"; | |
| 471 end | |
| 472 library_versions["libunbound"] = lunbound._LIBVER; | |
| 473 end | |
| 474 for name, version in sorted_pairs(module_versions) do | |
| 475 print(name..":"..string.rep(" ", longest_name-#name), version); | |
| 476 end | |
| 477 print(""); | |
| 478 print("# library versions"); | |
| 479 if require "net.server".event_base then | |
| 480 library_versions["libevent"] = require"luaevent".core.libevent_version(); | |
| 481 end | |
| 482 for name, version in sorted_pairs(library_versions) do | |
| 483 print(name..":"..string.rep(" ", longest_name-#name), version); | |
| 434 end | 484 end |
| 435 print(""); | 485 print(""); |
| 436 end | 486 end |
| 437 | 487 |
| 438 function commands.reload(arg) | 488 function commands.reload(arg) |
| 509 | 559 |
| 510 if ok then return 0; end | 560 if ok then return 0; end |
| 511 | 561 |
| 512 show_message(error_messages[msg]) | 562 show_message(error_messages[msg]) |
| 513 return 1; | 563 return 1; |
| 514 end | |
| 515 | |
| 516 local openssl; | |
| 517 | |
| 518 local cert_commands = {}; | |
| 519 | |
| 520 -- If a file already exists, ask if the user wants to use it or replace it | |
| 521 -- Backups the old file if replaced | |
| 522 local function use_existing(filename) | |
| 523 local attrs = lfs.attributes(filename); | |
| 524 if attrs then | |
| 525 if show_yesno(filename .. " exists, do you want to replace it? [y/n]") then | |
| 526 local backup = filename..".bkp~"..os.date("%FT%T", attrs.change); | |
| 527 os.rename(filename, backup); | |
| 528 show_message(filename.." backed up to "..backup); | |
| 529 else | |
| 530 -- Use the existing file | |
| 531 return true; | |
| 532 end | |
| 533 end | |
| 534 end | |
| 535 | |
| 536 local have_pposix, pposix = pcall(require, "util.pposix"); | |
| 537 local cert_basedir = prosody.paths.data == "." and "./certs" or prosody.paths.data; | |
| 538 if have_pposix and pposix.getuid() == 0 then | |
| 539 -- FIXME should be enough to check if this directory is writable | |
| 540 local cert_dir = configmanager.get("*", "certificates") or "certs"; | |
| 541 cert_basedir = configmanager.resolve_relative_path(prosody.paths.config, cert_dir); | |
| 542 end | |
| 543 | |
| 544 function cert_commands.config(arg) | |
| 545 if #arg >= 1 and arg[1] ~= "--help" then | |
| 546 local conf_filename = cert_basedir .. "/" .. arg[1] .. ".cnf"; | |
| 547 if use_existing(conf_filename) then | |
| 548 return nil, conf_filename; | |
| 549 end | |
| 550 local distinguished_name; | |
| 551 if arg[#arg]:find("^/") then | |
| 552 distinguished_name = table.remove(arg); | |
| 553 end | |
| 554 local conf = openssl.config.new(); | |
| 555 conf:from_prosody(prosody.hosts, configmanager, arg); | |
| 556 if distinguished_name then | |
| 557 local dn = {}; | |
| 558 for k, v in distinguished_name:gmatch("/([^=/]+)=([^/]+)") do | |
| 559 table.insert(dn, k); | |
| 560 dn[k] = v; | |
| 561 end | |
| 562 conf.distinguished_name = dn; | |
| 563 else | |
| 564 show_message("Please provide details to include in the certificate config file."); | |
| 565 show_message("Leave the field empty to use the default value or '.' to exclude the field.") | |
| 566 for _, k in ipairs(openssl._DN_order) do | |
| 567 local v = conf.distinguished_name[k]; | |
| 568 if v then | |
| 569 local nv = nil; | |
| 570 if k == "commonName" then | |
| 571 v = arg[1] | |
| 572 elseif k == "emailAddress" then | |
| 573 v = "xmpp@" .. arg[1]; | |
| 574 elseif k == "countryName" then | |
| 575 local tld = arg[1]:match"%.([a-z]+)$"; | |
| 576 if tld and #tld == 2 and tld ~= "uk" then | |
| 577 v = tld:upper(); | |
| 578 end | |
| 579 end | |
| 580 nv = show_prompt(("%s (%s):"):format(k, nv or v)); | |
| 581 nv = (not nv or nv == "") and v or nv; | |
| 582 if nv:find"[\192-\252][\128-\191]+" then | |
| 583 conf.req.string_mask = "utf8only" | |
| 584 end | |
| 585 conf.distinguished_name[k] = nv ~= "." and nv or nil; | |
| 586 end | |
| 587 end | |
| 588 end | |
| 589 local conf_file, err = io.open(conf_filename, "w"); | |
| 590 if not conf_file then | |
| 591 show_warning("Could not open OpenSSL config file for writing"); | |
| 592 show_warning(err); | |
| 593 os.exit(1); | |
| 594 end | |
| 595 conf_file:write(conf:serialize()); | |
| 596 conf_file:close(); | |
| 597 print(""); | |
| 598 show_message("Config written to " .. conf_filename); | |
| 599 return nil, conf_filename; | |
| 600 else | |
| 601 show_usage("cert config HOSTNAME [HOSTNAME+]", "Builds a certificate config file covering the supplied hostname(s)") | |
| 602 end | |
| 603 end | |
| 604 | |
| 605 function cert_commands.key(arg) | |
| 606 if #arg >= 1 and arg[1] ~= "--help" then | |
| 607 local key_filename = cert_basedir .. "/" .. arg[1] .. ".key"; | |
| 608 if use_existing(key_filename) then | |
| 609 return nil, key_filename; | |
| 610 end | |
| 611 os.remove(key_filename); -- This file, if it exists is unlikely to have write permissions | |
| 612 local key_size = tonumber(arg[2] or show_prompt("Choose key size (2048):") or 2048); | |
| 613 local old_umask = pposix.umask("0377"); | |
| 614 if openssl.genrsa{out=key_filename, key_size} then | |
| 615 os.execute(("chmod 400 '%s'"):format(key_filename)); | |
| 616 show_message("Key written to ".. key_filename); | |
| 617 pposix.umask(old_umask); | |
| 618 return nil, key_filename; | |
| 619 end | |
| 620 show_message("There was a problem, see OpenSSL output"); | |
| 621 else | |
| 622 show_usage("cert key HOSTNAME <bits>", "Generates a RSA key named HOSTNAME.key\n " | |
| 623 .."Prompts for a key size if none given") | |
| 624 end | |
| 625 end | |
| 626 | |
| 627 function cert_commands.request(arg) | |
| 628 if #arg >= 1 and arg[1] ~= "--help" then | |
| 629 local req_filename = cert_basedir .. "/" .. arg[1] .. ".req"; | |
| 630 if use_existing(req_filename) then | |
| 631 return nil, req_filename; | |
| 632 end | |
| 633 local _, key_filename = cert_commands.key({arg[1]}); | |
| 634 local _, conf_filename = cert_commands.config(arg); | |
| 635 if openssl.req{new=true, key=key_filename, utf8=true, sha256=true, config=conf_filename, out=req_filename} then | |
| 636 show_message("Certificate request written to ".. req_filename); | |
| 637 else | |
| 638 show_message("There was a problem, see OpenSSL output"); | |
| 639 end | |
| 640 else | |
| 641 show_usage("cert request HOSTNAME [HOSTNAME+]", "Generates a certificate request for the supplied hostname(s)") | |
| 642 end | |
| 643 end | |
| 644 | |
| 645 function cert_commands.generate(arg) | |
| 646 if #arg >= 1 and arg[1] ~= "--help" then | |
| 647 local cert_filename = cert_basedir .. "/" .. arg[1] .. ".crt"; | |
| 648 if use_existing(cert_filename) then | |
| 649 return nil, cert_filename; | |
| 650 end | |
| 651 local _, key_filename = cert_commands.key({arg[1]}); | |
| 652 local _, conf_filename = cert_commands.config(arg); | |
| 653 if key_filename and conf_filename and cert_filename | |
| 654 and openssl.req{new=true, x509=true, nodes=true, key=key_filename, | |
| 655 days=365, sha256=true, utf8=true, config=conf_filename, out=cert_filename} then | |
| 656 show_message("Certificate written to ".. cert_filename); | |
| 657 print(); | |
| 658 else | |
| 659 show_message("There was a problem, see OpenSSL output"); | |
| 660 end | |
| 661 else | |
| 662 show_usage("cert generate HOSTNAME [HOSTNAME+]", "Generates a self-signed certificate for the current hostname(s)") | |
| 663 end | |
| 664 end | |
| 665 | |
| 666 local function sh_esc(s) | |
| 667 return "'" .. s:gsub("'", "'\\''") .. "'"; | |
| 668 end | |
| 669 | |
| 670 local function copy(from, to, umask, owner, group) | |
| 671 local old_umask = umask and pposix.umask(umask); | |
| 672 local attrs = lfs.attributes(to); | |
| 673 if attrs then -- Move old file out of the way | |
| 674 local backup = to..".bkp~"..os.date("%FT%T", attrs.change); | |
| 675 os.rename(to, backup); | |
| 676 end | |
| 677 -- FIXME friendlier error handling, maybe move above backup back? | |
| 678 local input = assert(io.open(from)); | |
| 679 local output = assert(io.open(to, "w")); | |
| 680 local data = input:read(2^11); | |
| 681 while data and output:write(data) do | |
| 682 data = input:read(2^11); | |
| 683 end | |
| 684 assert(input:close()); | |
| 685 assert(output:close()); | |
| 686 if not prosody.installed then | |
| 687 -- FIXME this is possibly specific to GNU chown | |
| 688 os.execute(("chown -c --reference=%s %s"):format(sh_esc(cert_basedir), sh_esc(to))); | |
| 689 elseif owner and group then | |
| 690 local ok = os.execute(("chown %s:%s %s"):format(sh_esc(owner), sh_esc(group), sh_esc(to))); | |
| 691 assert(ok == true or ok == 0, "Failed to change ownership of "..to); | |
| 692 end | |
| 693 if old_umask then pposix.umask(old_umask); end | |
| 694 return true; | |
| 695 end | |
| 696 | |
| 697 function cert_commands.import(arg) | |
| 698 local hostnames = {}; | |
| 699 -- Move hostname arguments out of arg, the rest should be a list of paths | |
| 700 while arg[1] and prosody.hosts[ arg[1] ] do | |
| 701 table.insert(hostnames, table.remove(arg, 1)); | |
| 702 end | |
| 703 if hostnames[1] == nil then | |
| 704 local domains = os.getenv"RENEWED_DOMAINS"; -- Set if invoked via certbot | |
| 705 if domains then | |
| 706 for host in domains:gmatch("%S+") do | |
| 707 table.insert(hostnames, host); | |
| 708 end | |
| 709 else | |
| 710 for host in pairs(prosody.hosts) do | |
| 711 if host ~= "*" and configmanager.get(host, "enabled") ~= false then | |
| 712 table.insert(hostnames, host); | |
| 713 end | |
| 714 end | |
| 715 end | |
| 716 end | |
| 717 if not arg[1] or arg[1] == "--help" then -- Probably forgot the path | |
| 718 show_usage("cert import [HOSTNAME+] /path/to/certs [/other/paths/]+", | |
| 719 "Copies certificates to "..cert_basedir); | |
| 720 return 1; | |
| 721 end | |
| 722 local owner, group; | |
| 723 if pposix.getuid() == 0 then -- We need root to change ownership | |
| 724 owner = configmanager.get("*", "prosody_user") or "prosody"; | |
| 725 group = configmanager.get("*", "prosody_group") or owner; | |
| 726 end | |
| 727 local cm = require "core.certmanager"; | |
| 728 local imported = {}; | |
| 729 for _, host in ipairs(hostnames) do | |
| 730 for _, dir in ipairs(arg) do | |
| 731 local paths = cm.find_cert(dir, host); | |
| 732 if paths then | |
| 733 copy(paths.certificate, cert_basedir .. "/" .. host .. ".crt", nil, owner, group); | |
| 734 copy(paths.key, cert_basedir .. "/" .. host .. ".key", "0377", owner, group); | |
| 735 table.insert(imported, host); | |
| 736 else | |
| 737 -- TODO Say where we looked | |
| 738 show_warning("No certificate for host "..host.." found :("); | |
| 739 end | |
| 740 -- TODO Additional checks | |
| 741 -- Certificate names matches the hostname | |
| 742 -- Private key matches public key in certificate | |
| 743 end | |
| 744 end | |
| 745 if imported[1] then | |
| 746 show_message("Imported certificate and key for hosts "..table.concat(imported, ", ")); | |
| 747 local ok, err = prosodyctl.reload(); | |
| 748 if not ok and err ~= "not-running" then | |
| 749 show_message(error_messages[err]); | |
| 750 end | |
| 751 else | |
| 752 show_warning("No certificates imported :("); | |
| 753 return 1; | |
| 754 end | |
| 755 end | |
| 756 | |
| 757 function commands.cert(arg) | |
| 758 if #arg >= 1 and arg[1] ~= "--help" then | |
| 759 openssl = require "util.openssl"; | |
| 760 lfs = require "lfs"; | |
| 761 local cert_dir_attrs = lfs.attributes(cert_basedir); | |
| 762 if not cert_dir_attrs then | |
| 763 show_warning("The directory "..cert_basedir.." does not exist"); | |
| 764 return 1; -- TODO Should we create it? | |
| 765 end | |
| 766 local uid = pposix.getuid(); | |
| 767 if uid ~= 0 and uid ~= cert_dir_attrs.uid then | |
| 768 show_warning("The directory "..cert_basedir.." is not owned by the current user, won't be able to write files to it"); | |
| 769 return 1; | |
| 770 elseif not cert_dir_attrs.permissions then -- COMPAT with LuaFilesystem < 1.6.2 (hey CentOS!) | |
| 771 show_message("Unable to check permissions on "..cert_basedir.." (LuaFilesystem 1.6.2+ required)"); | |
| 772 show_message("Please confirm that Prosody (and only Prosody) can write to this directory)"); | |
| 773 elseif cert_dir_attrs.permissions:match("^%.w..%-..%-.$") then | |
| 774 show_warning("The directory "..cert_basedir.." not only writable by its owner"); | |
| 775 return 1; | |
| 776 end | |
| 777 local subcmd = table.remove(arg, 1); | |
| 778 if type(cert_commands[subcmd]) == "function" then | |
| 779 if subcmd ~= "import" then -- hostnames are optional for import | |
| 780 if not arg[1] then | |
| 781 show_message"You need to supply at least one hostname" | |
| 782 arg = { "--help" }; | |
| 783 end | |
| 784 if arg[1] ~= "--help" and not prosody.hosts[arg[1]] then | |
| 785 show_message(error_messages["no-such-host"]); | |
| 786 return 1; | |
| 787 end | |
| 788 end | |
| 789 return cert_commands[subcmd](arg); | |
| 790 elseif subcmd == "check" then | |
| 791 return commands.check({"certs"}); | |
| 792 end | |
| 793 end | |
| 794 show_usage("cert config|request|generate|key|import", "Helpers for generating X.509 certificates and keys.") | |
| 795 for _, cmd in pairs(cert_commands) do | |
| 796 print() | |
| 797 cmd{ "--help" } | |
| 798 end | |
| 799 end | |
| 800 | |
| 801 function commands.check(arg) | |
| 802 if arg[1] == "--help" then | |
| 803 show_usage([[check]], [[Perform basic checks on your Prosody installation]]); | |
| 804 return 1; | |
| 805 end | |
| 806 local what = table.remove(arg, 1); | |
| 807 local set = require "util.set"; | |
| 808 local it = require "util.iterators"; | |
| 809 local ok = true; | |
| 810 local function disabled_hosts(host, conf) return host ~= "*" and conf.enabled ~= false; end | |
| 811 local function enabled_hosts() return it.filter(disabled_hosts, pairs(configmanager.getconfig())); end | |
| 812 if not (what == nil or what == "disabled" or what == "config" or what == "dns" or what == "certs") then | |
| 813 show_warning("Don't know how to check '%s'. Try one of 'config', 'dns', 'certs' or 'disabled'.", what); | |
| 814 return 1; | |
| 815 end | |
| 816 if not what or what == "disabled" then | |
| 817 local disabled_hosts_set = set.new(); | |
| 818 for host, host_options in it.filter("*", pairs(configmanager.getconfig())) do | |
| 819 if host_options.enabled == false then | |
| 820 disabled_hosts_set:add(host); | |
| 821 end | |
| 822 end | |
| 823 if not disabled_hosts_set:empty() then | |
| 824 local msg = "Checks will be skipped for these disabled hosts: %s"; | |
| 825 if what then msg = "These hosts are disabled: %s"; end | |
| 826 show_warning(msg, tostring(disabled_hosts_set)); | |
| 827 if what then return 0; end | |
| 828 print"" | |
| 829 end | |
| 830 end | |
| 831 if not what or what == "config" then | |
| 832 print("Checking config..."); | |
| 833 local deprecated = set.new({ | |
| 834 "bosh_ports", "disallow_s2s", "no_daemonize", "anonymous_login", "require_encryption", | |
| 835 "vcard_compatibility", | |
| 836 }); | |
| 837 local known_global_options = set.new({ | |
| 838 "pidfile", "log", "plugin_paths", "prosody_user", "prosody_group", "daemonize", | |
| 839 "umask", "prosodyctl_timeout", "use_ipv6", "use_libevent", "network_settings", | |
| 840 "network_backend", "http_default_host", | |
| 841 "statistics_interval", "statistics", "statistics_config", | |
| 842 }); | |
| 843 local config = configmanager.getconfig(); | |
| 844 -- Check that we have any global options (caused by putting a host at the top) | |
| 845 if it.count(it.filter("log", pairs(config["*"]))) == 0 then | |
| 846 ok = false; | |
| 847 print(""); | |
| 848 print(" No global options defined. Perhaps you have put a host definition at the top") | |
| 849 print(" of the config file? They should be at the bottom, see https://prosody.im/doc/configure#overview"); | |
| 850 end | |
| 851 if it.count(enabled_hosts()) == 0 then | |
| 852 ok = false; | |
| 853 print(""); | |
| 854 if it.count(it.filter("*", pairs(config))) == 0 then | |
| 855 print(" No hosts are defined, please add at least one VirtualHost section") | |
| 856 elseif config["*"]["enabled"] == false then | |
| 857 print(" No hosts are enabled. Remove enabled = false from the global section or put enabled = true under at least one VirtualHost section") | |
| 858 else | |
| 859 print(" All hosts are disabled. Remove enabled = false from at least one VirtualHost section") | |
| 860 end | |
| 861 end | |
| 862 if not config["*"].modules_enabled then | |
| 863 print(" No global modules_enabled is set?"); | |
| 864 local suggested_global_modules; | |
| 865 for host, options in enabled_hosts() do --luacheck: ignore 213/host | |
| 866 if not options.component_module and options.modules_enabled then | |
| 867 suggested_global_modules = set.intersection(suggested_global_modules or set.new(options.modules_enabled), set.new(options.modules_enabled)); | |
| 868 end | |
| 869 end | |
| 870 if suggested_global_modules and not suggested_global_modules:empty() then | |
| 871 print(" Consider moving these modules into modules_enabled in the global section:") | |
| 872 print(" "..tostring(suggested_global_modules / function (x) return ("%q"):format(x) end)); | |
| 873 end | |
| 874 print(); | |
| 875 end | |
| 876 | |
| 877 do -- Check for modules enabled both normally and as components | |
| 878 local modules = set.new(config["*"]["modules_enabled"]); | |
| 879 for host, options in enabled_hosts() do | |
| 880 local component_module = options.component_module; | |
| 881 if component_module and modules:contains(component_module) then | |
| 882 print((" mod_%s is enabled both in modules_enabled and as Component %q %q"):format(component_module, host, component_module)); | |
| 883 print(" This means the service is enabled on all VirtualHosts as well as the Component."); | |
| 884 print(" Are you sure this what you want? It may cause unexpected behaviour."); | |
| 885 end | |
| 886 end | |
| 887 end | |
| 888 | |
| 889 -- Check for global options under hosts | |
| 890 local global_options = set.new(it.to_array(it.keys(config["*"]))); | |
| 891 local deprecated_global_options = set.intersection(global_options, deprecated); | |
| 892 if not deprecated_global_options:empty() then | |
| 893 print(""); | |
| 894 print(" You have some deprecated options in the global section:"); | |
| 895 print(" "..tostring(deprecated_global_options)) | |
| 896 ok = false; | |
| 897 end | |
| 898 for host, options in it.filter(function (h) return h ~= "*" end, pairs(configmanager.getconfig())) do | |
| 899 local host_options = set.new(it.to_array(it.keys(options))); | |
| 900 local misplaced_options = set.intersection(host_options, known_global_options); | |
| 901 for name in pairs(options) do | |
| 902 if name:match("^interfaces?") | |
| 903 or name:match("_ports?$") or name:match("_interfaces?$") | |
| 904 or (name:match("_ssl$") and not name:match("^[cs]2s_ssl$")) then | |
| 905 misplaced_options:add(name); | |
| 906 end | |
| 907 end | |
| 908 if not misplaced_options:empty() then | |
| 909 ok = false; | |
| 910 print(""); | |
| 911 local n = it.count(misplaced_options); | |
| 912 print(" You have "..n.." option"..(n>1 and "s " or " ").."set under "..host.." that should be"); | |
| 913 print(" in the global section of the config file, above any VirtualHost or Component definitions,") | |
| 914 print(" see https://prosody.im/doc/configure#overview for more information.") | |
| 915 print(""); | |
| 916 print(" You need to move the following option"..(n>1 and "s" or "")..": "..table.concat(it.to_array(misplaced_options), ", ")); | |
| 917 end | |
| 918 end | |
| 919 for host, options in enabled_hosts() do | |
| 920 local host_options = set.new(it.to_array(it.keys(options))); | |
| 921 local subdomain = host:match("^[^.]+"); | |
| 922 if not(host_options:contains("component_module")) and (subdomain == "jabber" or subdomain == "xmpp" | |
| 923 or subdomain == "chat" or subdomain == "im") then | |
| 924 print(""); | |
| 925 print(" Suggestion: If "..host.. " is a new host with no real users yet, consider renaming it now to"); | |
| 926 print(" "..host:gsub("^[^.]+%.", "")..". You can use SRV records to redirect XMPP clients and servers to "..host.."."); | |
| 927 print(" For more information see: https://prosody.im/doc/dns"); | |
| 928 end | |
| 929 end | |
| 930 local all_modules = set.new(config["*"].modules_enabled); | |
| 931 local all_options = set.new(it.to_array(it.keys(config["*"]))); | |
| 932 for host in enabled_hosts() do | |
| 933 all_options:include(set.new(it.to_array(it.keys(config[host])))); | |
| 934 all_modules:include(set.new(config[host].modules_enabled)); | |
| 935 end | |
| 936 for mod in all_modules do | |
| 937 if mod:match("^mod_") then | |
| 938 print(""); | |
| 939 print(" Modules in modules_enabled should not have the 'mod_' prefix included."); | |
| 940 print(" Change '"..mod.."' to '"..mod:match("^mod_(.*)").."'."); | |
| 941 elseif mod:match("^auth_") then | |
| 942 print(""); | |
| 943 print(" Authentication modules should not be added to modules_enabled,"); | |
| 944 print(" but be specified in the 'authentication' option."); | |
| 945 print(" Remove '"..mod.."' from modules_enabled and instead add"); | |
| 946 print(" authentication = '"..mod:match("^auth_(.*)").."'"); | |
| 947 print(" For more information see https://prosody.im/doc/authentication"); | |
| 948 elseif mod:match("^storage_") then | |
| 949 print(""); | |
| 950 print(" storage modules should not be added to modules_enabled,"); | |
| 951 print(" but be specified in the 'storage' option."); | |
| 952 print(" Remove '"..mod.."' from modules_enabled and instead add"); | |
| 953 print(" storage = '"..mod:match("^storage_(.*)").."'"); | |
| 954 print(" For more information see https://prosody.im/doc/storage"); | |
| 955 end | |
| 956 end | |
| 957 if all_modules:contains("vcard") and all_modules:contains("vcard_legacy") then | |
| 958 print(""); | |
| 959 print(" Both mod_vcard_legacy and mod_vcard are enabled but they conflict"); | |
| 960 print(" with each other. Remove one."); | |
| 961 end | |
| 962 if all_modules:contains("pep") and all_modules:contains("pep_simple") then | |
| 963 print(""); | |
| 964 print(" Both mod_pep_simple and mod_pep are enabled but they conflict"); | |
| 965 print(" with each other. Remove one."); | |
| 966 end | |
| 967 for host, host_config in pairs(config) do --luacheck: ignore 213/host | |
| 968 if type(rawget(host_config, "storage")) == "string" and rawget(host_config, "default_storage") then | |
| 969 print(""); | |
| 970 print(" The 'default_storage' option is not needed if 'storage' is set to a string."); | |
| 971 break; | |
| 972 end | |
| 973 end | |
| 974 local require_encryption = set.intersection(all_options, set.new({ | |
| 975 "require_encryption", "c2s_require_encryption", "s2s_require_encryption" | |
| 976 })):empty(); | |
| 977 local ssl = dependencies.softreq"ssl"; | |
| 978 if not ssl then | |
| 979 if not require_encryption then | |
| 980 print(""); | |
| 981 print(" You require encryption but LuaSec is not available."); | |
| 982 print(" Connections will fail."); | |
| 983 ok = false; | |
| 984 end | |
| 985 elseif not ssl.loadcertificate then | |
| 986 if all_options:contains("s2s_secure_auth") then | |
| 987 print(""); | |
| 988 print(" You have set s2s_secure_auth but your version of LuaSec does "); | |
| 989 print(" not support certificate validation, so all s2s connections will"); | |
| 990 print(" fail."); | |
| 991 ok = false; | |
| 992 elseif all_options:contains("s2s_secure_domains") then | |
| 993 local secure_domains = set.new(); | |
| 994 for host in enabled_hosts() do | |
| 995 if config[host].s2s_secure_auth == true then | |
| 996 secure_domains:add("*"); | |
| 997 else | |
| 998 secure_domains:include(set.new(config[host].s2s_secure_domains)); | |
| 999 end | |
| 1000 end | |
| 1001 if not secure_domains:empty() then | |
| 1002 print(""); | |
| 1003 print(" You have set s2s_secure_domains but your version of LuaSec does "); | |
| 1004 print(" not support certificate validation, so s2s connections to/from "); | |
| 1005 print(" these domains will fail."); | |
| 1006 ok = false; | |
| 1007 end | |
| 1008 end | |
| 1009 elseif require_encryption and not all_modules:contains("tls") then | |
| 1010 print(""); | |
| 1011 print(" You require encryption but mod_tls is not enabled."); | |
| 1012 print(" Connections will fail."); | |
| 1013 ok = false; | |
| 1014 end | |
| 1015 | |
| 1016 print("Done.\n"); | |
| 1017 end | |
| 1018 if not what or what == "dns" then | |
| 1019 local dns = require "net.dns"; | |
| 1020 local idna = require "util.encodings".idna; | |
| 1021 local ip = require "util.ip"; | |
| 1022 local c2s_ports = set.new(configmanager.get("*", "c2s_ports") or {5222}); | |
| 1023 local s2s_ports = set.new(configmanager.get("*", "s2s_ports") or {5269}); | |
| 1024 | |
| 1025 local c2s_srv_required, s2s_srv_required; | |
| 1026 if not c2s_ports:contains(5222) then | |
| 1027 c2s_srv_required = true; | |
| 1028 end | |
| 1029 if not s2s_ports:contains(5269) then | |
| 1030 s2s_srv_required = true; | |
| 1031 end | |
| 1032 | |
| 1033 local problem_hosts = set.new(); | |
| 1034 | |
| 1035 local external_addresses, internal_addresses = set.new(), set.new(); | |
| 1036 | |
| 1037 local fqdn = socket.dns.tohostname(socket.dns.gethostname()); | |
| 1038 if fqdn then | |
| 1039 do | |
| 1040 local res = dns.lookup(idna.to_ascii(fqdn), "A"); | |
| 1041 if res then | |
| 1042 for _, record in ipairs(res) do | |
| 1043 external_addresses:add(record.a); | |
| 1044 end | |
| 1045 end | |
| 1046 end | |
| 1047 do | |
| 1048 local res = dns.lookup(idna.to_ascii(fqdn), "AAAA"); | |
| 1049 if res then | |
| 1050 for _, record in ipairs(res) do | |
| 1051 external_addresses:add(record.aaaa); | |
| 1052 end | |
| 1053 end | |
| 1054 end | |
| 1055 end | |
| 1056 | |
| 1057 local local_addresses = require"util.net".local_addresses() or {}; | |
| 1058 | |
| 1059 for addr in it.values(local_addresses) do | |
| 1060 if not ip.new_ip(addr).private then | |
| 1061 external_addresses:add(addr); | |
| 1062 else | |
| 1063 internal_addresses:add(addr); | |
| 1064 end | |
| 1065 end | |
| 1066 | |
| 1067 if external_addresses:empty() then | |
| 1068 print(""); | |
| 1069 print(" Failed to determine the external addresses of this server. Checks may be inaccurate."); | |
| 1070 c2s_srv_required, s2s_srv_required = true, true; | |
| 1071 end | |
| 1072 | |
| 1073 local v6_supported = not not socket.tcp6; | |
| 1074 | |
| 1075 for jid, host_options in enabled_hosts() do | |
| 1076 local all_targets_ok, some_targets_ok = true, false; | |
| 1077 local node, host = jid_split(jid); | |
| 1078 | |
| 1079 local modules, component_module = modulemanager.get_modules_for_host(host); | |
| 1080 if component_module then | |
| 1081 modules:add(component_module); | |
| 1082 end | |
| 1083 | |
| 1084 local is_component = not not host_options.component_module; | |
| 1085 print("Checking DNS for "..(is_component and "component" or "host").." "..jid.."..."); | |
| 1086 if node then | |
| 1087 print("Only the domain part ("..host..") is used in DNS.") | |
| 1088 end | |
| 1089 local target_hosts = set.new(); | |
| 1090 if modules:contains("c2s") then | |
| 1091 local res = dns.lookup("_xmpp-client._tcp."..idna.to_ascii(host)..".", "SRV"); | |
| 1092 if res then | |
| 1093 for _, record in ipairs(res) do | |
| 1094 target_hosts:add(record.srv.target); | |
| 1095 if not c2s_ports:contains(record.srv.port) then | |
| 1096 print(" SRV target "..record.srv.target.." contains unknown client port: "..record.srv.port); | |
| 1097 end | |
| 1098 end | |
| 1099 else | |
| 1100 if c2s_srv_required then | |
| 1101 print(" No _xmpp-client SRV record found for "..host..", but it looks like you need one."); | |
| 1102 all_targets_ok = false; | |
| 1103 else | |
| 1104 target_hosts:add(host); | |
| 1105 end | |
| 1106 end | |
| 1107 end | |
| 1108 if modules:contains("s2s") then | |
| 1109 local res = dns.lookup("_xmpp-server._tcp."..idna.to_ascii(host)..".", "SRV"); | |
| 1110 if res then | |
| 1111 for _, record in ipairs(res) do | |
| 1112 target_hosts:add(record.srv.target); | |
| 1113 if not s2s_ports:contains(record.srv.port) then | |
| 1114 print(" SRV target "..record.srv.target.." contains unknown server port: "..record.srv.port); | |
| 1115 end | |
| 1116 end | |
| 1117 else | |
| 1118 if s2s_srv_required then | |
| 1119 print(" No _xmpp-server SRV record found for "..host..", but it looks like you need one."); | |
| 1120 all_targets_ok = false; | |
| 1121 else | |
| 1122 target_hosts:add(host); | |
| 1123 end | |
| 1124 end | |
| 1125 end | |
| 1126 if target_hosts:empty() then | |
| 1127 target_hosts:add(host); | |
| 1128 end | |
| 1129 | |
| 1130 if target_hosts:contains("localhost") then | |
| 1131 print(" Target 'localhost' cannot be accessed from other servers"); | |
| 1132 target_hosts:remove("localhost"); | |
| 1133 end | |
| 1134 | |
| 1135 if modules:contains("proxy65") then | |
| 1136 local proxy65_target = configmanager.get(host, "proxy65_address") or host; | |
| 1137 if type(proxy65_target) == "string" then | |
| 1138 local A, AAAA = dns.lookup(idna.to_ascii(proxy65_target), "A"), dns.lookup(idna.to_ascii(proxy65_target), "AAAA"); | |
| 1139 local prob = {}; | |
| 1140 if not A then | |
| 1141 table.insert(prob, "A"); | |
| 1142 end | |
| 1143 if v6_supported and not AAAA then | |
| 1144 table.insert(prob, "AAAA"); | |
| 1145 end | |
| 1146 if #prob > 0 then | |
| 1147 print(" File transfer proxy "..proxy65_target.." has no "..table.concat(prob, "/") | |
| 1148 .." record. Create one or set 'proxy65_address' to the correct host/IP."); | |
| 1149 end | |
| 1150 else | |
| 1151 print(" proxy65_address for "..host.." should be set to a string, unable to perform DNS check"); | |
| 1152 end | |
| 1153 end | |
| 1154 | |
| 1155 for target_host in target_hosts do | |
| 1156 local host_ok_v4, host_ok_v6; | |
| 1157 do | |
| 1158 local res = dns.lookup(idna.to_ascii(target_host), "A"); | |
| 1159 if res then | |
| 1160 for _, record in ipairs(res) do | |
| 1161 if external_addresses:contains(record.a) then | |
| 1162 some_targets_ok = true; | |
| 1163 host_ok_v4 = true; | |
| 1164 elseif internal_addresses:contains(record.a) then | |
| 1165 host_ok_v4 = true; | |
| 1166 some_targets_ok = true; | |
| 1167 print(" "..target_host.." A record points to internal address, external connections might fail"); | |
| 1168 else | |
| 1169 print(" "..target_host.." A record points to unknown address "..record.a); | |
| 1170 all_targets_ok = false; | |
| 1171 end | |
| 1172 end | |
| 1173 end | |
| 1174 end | |
| 1175 do | |
| 1176 local res = dns.lookup(idna.to_ascii(target_host), "AAAA"); | |
| 1177 if res then | |
| 1178 for _, record in ipairs(res) do | |
| 1179 if external_addresses:contains(record.aaaa) then | |
| 1180 some_targets_ok = true; | |
| 1181 host_ok_v6 = true; | |
| 1182 elseif internal_addresses:contains(record.aaaa) then | |
| 1183 host_ok_v6 = true; | |
| 1184 some_targets_ok = true; | |
| 1185 print(" "..target_host.." AAAA record points to internal address, external connections might fail"); | |
| 1186 else | |
| 1187 print(" "..target_host.." AAAA record points to unknown address "..record.aaaa); | |
| 1188 all_targets_ok = false; | |
| 1189 end | |
| 1190 end | |
| 1191 end | |
| 1192 end | |
| 1193 | |
| 1194 local bad_protos = {} | |
| 1195 if not host_ok_v4 then | |
| 1196 table.insert(bad_protos, "IPv4"); | |
| 1197 end | |
| 1198 if not host_ok_v6 then | |
| 1199 table.insert(bad_protos, "IPv6"); | |
| 1200 end | |
| 1201 if #bad_protos > 0 then | |
| 1202 print(" Host "..target_host.." does not seem to resolve to this server ("..table.concat(bad_protos, "/")..")"); | |
| 1203 end | |
| 1204 if host_ok_v6 and not v6_supported then | |
| 1205 print(" Host "..target_host.." has AAAA records, but your version of LuaSocket does not support IPv6."); | |
| 1206 print(" Please see https://prosody.im/doc/ipv6 for more information."); | |
| 1207 end | |
| 1208 end | |
| 1209 if not all_targets_ok then | |
| 1210 print(" "..(some_targets_ok and "Only some" or "No").." targets for "..host.." appear to resolve to this server."); | |
| 1211 if is_component then | |
| 1212 print(" DNS records are necessary if you want users on other servers to access this component."); | |
| 1213 end | |
| 1214 problem_hosts:add(host); | |
| 1215 end | |
| 1216 print(""); | |
| 1217 end | |
| 1218 if not problem_hosts:empty() then | |
| 1219 print(""); | |
| 1220 print("For more information about DNS configuration please see https://prosody.im/doc/dns"); | |
| 1221 print(""); | |
| 1222 ok = false; | |
| 1223 end | |
| 1224 end | |
| 1225 if not what or what == "certs" then | |
| 1226 local cert_ok; | |
| 1227 print"Checking certificates..." | |
| 1228 local x509_verify_identity = require"util.x509".verify_identity; | |
| 1229 local create_context = require "core.certmanager".create_context; | |
| 1230 local ssl = dependencies.softreq"ssl"; | |
| 1231 -- local datetime_parse = require"util.datetime".parse_x509; | |
| 1232 local load_cert = ssl and ssl.loadcertificate; | |
| 1233 -- or ssl.cert_from_pem | |
| 1234 if not ssl then | |
| 1235 print("LuaSec not available, can't perform certificate checks") | |
| 1236 if what == "certs" then cert_ok = false end | |
| 1237 elseif not load_cert then | |
| 1238 print("This version of LuaSec (" .. ssl._VERSION .. ") does not support certificate checking"); | |
| 1239 cert_ok = false | |
| 1240 else | |
| 1241 local function skip_bare_jid_hosts(host) | |
| 1242 if jid_split(host) then | |
| 1243 -- See issue #779 | |
| 1244 return false; | |
| 1245 end | |
| 1246 return true; | |
| 1247 end | |
| 1248 for host in it.filter(skip_bare_jid_hosts, enabled_hosts()) do | |
| 1249 print("Checking certificate for "..host); | |
| 1250 -- First, let's find out what certificate this host uses. | |
| 1251 local host_ssl_config = configmanager.rawget(host, "ssl") | |
| 1252 or configmanager.rawget(host:match("%.(.*)"), "ssl"); | |
| 1253 local global_ssl_config = configmanager.rawget("*", "ssl"); | |
| 1254 local ok, err, ssl_config = create_context(host, "server", host_ssl_config, global_ssl_config); | |
| 1255 if not ok then | |
| 1256 print(" Error: "..err); | |
| 1257 cert_ok = false | |
| 1258 elseif not ssl_config.certificate then | |
| 1259 print(" No 'certificate' found for "..host) | |
| 1260 cert_ok = false | |
| 1261 elseif not ssl_config.key then | |
| 1262 print(" No 'key' found for "..host) | |
| 1263 cert_ok = false | |
| 1264 else | |
| 1265 local key, err = io.open(ssl_config.key); -- Permissions check only | |
| 1266 if not key then | |
| 1267 print(" Could not open "..ssl_config.key..": "..err); | |
| 1268 cert_ok = false | |
| 1269 else | |
| 1270 key:close(); | |
| 1271 end | |
| 1272 local cert_fh, err = io.open(ssl_config.certificate); -- Load the file. | |
| 1273 if not cert_fh then | |
| 1274 print(" Could not open "..ssl_config.certificate..": "..err); | |
| 1275 cert_ok = false | |
| 1276 else | |
| 1277 print(" Certificate: "..ssl_config.certificate) | |
| 1278 local cert = load_cert(cert_fh:read"*a"); cert_fh:close(); | |
| 1279 if not cert:validat(os.time()) then | |
| 1280 print(" Certificate has expired.") | |
| 1281 cert_ok = false | |
| 1282 elseif not cert:validat(os.time() + 86400) then | |
| 1283 print(" Certificate expires within one day.") | |
| 1284 cert_ok = false | |
| 1285 elseif not cert:validat(os.time() + 86400*7) then | |
| 1286 print(" Certificate expires within one week.") | |
| 1287 elseif not cert:validat(os.time() + 86400*31) then | |
| 1288 print(" Certificate expires within one month.") | |
| 1289 end | |
| 1290 if configmanager.get(host, "component_module") == nil | |
| 1291 and not x509_verify_identity(host, "_xmpp-client", cert) then | |
| 1292 print(" Not valid for client connections to "..host..".") | |
| 1293 cert_ok = false | |
| 1294 end | |
| 1295 if (not (configmanager.get(host, "anonymous_login") | |
| 1296 or configmanager.get(host, "authentication") == "anonymous")) | |
| 1297 and not x509_verify_identity(host, "_xmpp-server", cert) then | |
| 1298 print(" Not valid for server-to-server connections to "..host..".") | |
| 1299 cert_ok = false | |
| 1300 end | |
| 1301 end | |
| 1302 end | |
| 1303 end | |
| 1304 end | |
| 1305 if cert_ok == false then | |
| 1306 print("") | |
| 1307 print("For more information about certificates please see https://prosody.im/doc/certificates"); | |
| 1308 ok = false | |
| 1309 end | |
| 1310 print("") | |
| 1311 end | |
| 1312 if not ok then | |
| 1313 print("Problems found, see above."); | |
| 1314 else | |
| 1315 print("All checks passed, congratulations!"); | |
| 1316 end | |
| 1317 return ok and 0 or 2; | |
| 1318 end | 564 end |
| 1319 | 565 |
| 1320 --------------------- | 566 --------------------- |
| 1321 | 567 |
| 1322 local async = require "util.async"; | 568 local async = require "util.async"; |
| 1338 show_message("Failed to load module '"..module_name.."': "..err); | 584 show_message("Failed to load module '"..module_name.."': "..err); |
| 1339 os.exit(1); | 585 os.exit(1); |
| 1340 end | 586 end |
| 1341 end | 587 end |
| 1342 | 588 |
| 1343 table.remove(arg, 1); | |
| 1344 | |
| 1345 local module = modulemanager.get_module("*", module_name); | 589 local module = modulemanager.get_module("*", module_name); |
| 1346 if not module then | 590 if not module then |
| 1347 show_message("Failed to load module '"..module_name.."': Unknown error"); | 591 show_message("Failed to load module '"..module_name.."': Unknown error"); |
| 1348 os.exit(1); | 592 os.exit(1); |
| 1349 end | 593 end |
| 1365 show_message("Failed to execute command: "..error_messages[ret]); | 609 show_message("Failed to execute command: "..error_messages[ret]); |
| 1366 os.exit(1); -- :( | 610 os.exit(1); -- :( |
| 1367 end | 611 end |
| 1368 end | 612 end |
| 1369 | 613 |
| 614 if command and not commands[command] then | |
| 615 local ok, command_module = pcall(require, "util.prosodyctl."..command); | |
| 616 if ok and command_module[command] then | |
| 617 commands[command] = command_module[command]; | |
| 618 end | |
| 619 end | |
| 620 | |
| 1370 if not commands[command] then -- Show help for all commands | 621 if not commands[command] then -- Show help for all commands |
| 1371 function show_usage(usage, desc) | 622 function show_usage(usage, desc) |
| 1372 print(" "..usage); | 623 print(" "..usage); |
| 1373 print(" "..desc); | 624 print(" "..desc); |
| 1374 end | 625 end |
| 1378 print("Usage: "..arg[0].." COMMAND [OPTIONS]"); | 629 print("Usage: "..arg[0].." COMMAND [OPTIONS]"); |
| 1379 print(""); | 630 print(""); |
| 1380 print("Where COMMAND may be one of:\n"); | 631 print("Where COMMAND may be one of:\n"); |
| 1381 | 632 |
| 1382 local hidden_commands = require "util.set".new{ "register", "unregister", "addplugin" }; | 633 local hidden_commands = require "util.set".new{ "register", "unregister", "addplugin" }; |
| 1383 local commands_order = { "adduser", "passwd", "deluser", "start", "stop", "restart", "reload", "about" }; | 634 local commands_order = { "install", "remove", "list", "adduser", "passwd", "deluser", "start", "stop", "restart", "reload", |
| 635 "about" }; | |
| 1384 | 636 |
| 1385 local done = {}; | 637 local done = {}; |
| 1386 | 638 |
| 1387 for _, command_name in ipairs(commands_order) do | 639 for _, command_name in ipairs(commands_order) do |
| 1388 local command_func = commands[command_name]; | 640 local command_func = commands[command_name]; |
| 1403 | 655 |
| 1404 | 656 |
| 1405 os.exit(0); | 657 os.exit(0); |
| 1406 end | 658 end |
| 1407 | 659 |
| 1408 os.exit(commands[command]({ select(2, unpack(arg)) })); | 660 os.exit(commands[command](arg)); |
| 1409 end, watchers); | 661 end, watchers); |
| 1410 | 662 |
| 1411 command_runner:run(true); | 663 command_runner:run(true); |