From 2fb13bad659cb8dede30a2aa4f3eedfb4dd02b1a Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Sat, 2 May 2009 17:03:19 +0100 Subject: prosodyctl, util.prosodyctl: New prosodyctl utility for managing Prosody servers --- prosodyctl | 389 ++++++++++++++++++++++++++++++++++++++++++++++++++++ util/prosodyctl.lua | 113 +++++++++++++++ 2 files changed, 502 insertions(+) create mode 100755 prosodyctl create mode 100644 util/prosodyctl.lua diff --git a/prosodyctl b/prosodyctl new file mode 100755 index 00000000..db607833 --- /dev/null +++ b/prosodyctl @@ -0,0 +1,389 @@ +#!/usr/bin/env lua +-- Prosody IM v0.4 +-- Copyright (C) 2008-2009 Matthew Wild +-- Copyright (C) 2008-2009 Waqas Hussain +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- + +-- prosodyctl - command-line controller for Prosody XMPP server + +-- Will be modified by configure script if run -- + +CFG_SOURCEDIR=nil; +CFG_CONFIGDIR=os.getenv("PROSODY_CFGDIR"); +CFG_PLUGINDIR=nil; +CFG_DATADIR=os.getenv("PROSODY_DATADIR"); + +-- -- -- -- -- -- -- ---- -- -- -- -- -- -- -- -- + +if CFG_SOURCEDIR then + package.path = CFG_SOURCEDIR.."/?.lua;"..package.path + package.cpath = CFG_SOURCEDIR.."/?.so;"..package.cpath +end + +if CFG_DATADIR then + if os.getenv("HOME") then + CFG_DATADIR = CFG_DATADIR:gsub("^~", os.getenv("HOME")); + end +end + +-- Required to be able to find packages installed with luarocks +pcall(require, "luarocks.require") + + +config = require "core.configmanager" + +do + -- TODO: Check for other formats when we add support for them + -- Use lfs? Make a new conf/ dir? + local ok, level, err = config.load((CFG_CONFIGDIR or ".").."/prosody.cfg.lua"); + if not ok then + print("\n"); + print("**************************"); + if level == "parser" then + print("A problem occured while reading the config file "..(CFG_CONFIGDIR or ".").."/prosody.cfg.lua"); + local err_line, err_message = tostring(err):match("%[string .-%]:(%d*): (.*)"); + print("Error"..(err_line and (" on line "..err_line) or "")..": "..(err_message or tostring(err))); + print(""); + elseif level == "file" then + print("Prosody was unable to find the configuration file."); + print("We looked for: "..(CFG_CONFIGDIR or ".").."/prosody.cfg.lua"); + print("A sample config file is included in the Prosody download called prosody.cfg.lua.dist"); + print("Copy or rename it to prosody.cfg.lua and edit as necessary."); + end + print("More help on configuring Prosody can be found at http://prosody.im/doc/configure"); + print("Good luck!"); + print("**************************"); + print(""); + os.exit(1); + end +end + +local error_messages = setmetatable({ + ["invalid-username"] = "The given username is invalid in a Jabber ID"; + ["invalid-hostname"] = "The given hostname is invalid"; + ["no-password"] = "No password was supplied"; + ["no-such-user"] = "The given user does not exist on the server"; + }, { __index = function (t,k) return "Error: "..(tostring(k):gsub("%-", " "):gsub("^.", string.upper)); end }); + +hosts = {}; + +require "core.hostmanager" +require "core.eventmanager".fire_event("server-starting"); + +require "util.prosodyctl" +----------------------- + +function show_message(msg, ...) + print(msg:format(...)); +end + +function show_warning(msg, ...) + print(msg:format(...)); +end + +function show_usage(usage, desc) + print("Usage: "..arg[0].." "..usage); + if desc then + print(" "..desc); + end +end + +local function getchar(n) + os.execute("stty raw -echo"); + local char = io.read(n or 1); + os.execute("stty sane"); + return char; +end + +local function getpass() + os.execute("stty -echo"); + local pass = io.read("*l"); + os.execute("stty sane"); + io.write("\n"); + return pass; +end + +function show_yesno(prompt) + io.write(prompt, " "); + local choice = getchar():lower(); + io.write("\n"); + if not choice:match("%a") then + choice = prompt:match("%[.-(%U).-%]$"); + if not choice then return nil; end + end + return (choice == "y"); +end + +local function read_password() + local password; + while true do + io.write("Enter new password: "); + password = getpass(); + io.write("Retype new password: "); + if getpass() ~= password then + if not show_yesno [=[Passwords did not match, try again? [Y/n]]=] then + return; + end + else + break; + end + end + return password; +end +----------------------- +local commands = {}; +local command = arg[1]; + +function commands.adduser(arg) + if not arg[1] or arg[1] == "--help" then + show_usage([[adduser JID]], [[Create the specified user account in Prosody]]); + return 1; + end + local user, host = arg[1]:match("([^@]+)@(.+)"); + if not user and host then + show_message [[Failed to understand JID, please supply the JID you want to create]] + show_usage [[adduser user@host]] + return 1; + end + + if not host then + show_message [[Please specify a JID, including a host. e.g. alice@example.com]]; + return 1; + end + + if prosodyctl.user_exists{ user = user, host = host } then + show_message [[That user already exists]]; + return 1; + end + + if not hosts[host] then + show_warning("The host '%s' is not listed in the configuration file (or is not enabled).", host) + show_warning("The user will not be able to log in until this is changed."); + end + + local password = read_password(); + if not password then return 1; end + + local ok, msg = prosodyctl.adduser { user = user, host = host, password = password }; + + if ok then return 0; end + + show_message(error_messages[msg]) + return 1; +end + +function commands.passwd(arg) + if not arg[1] or arg[1] == "--help" then + show_usage([[passwd JID]], [[Set the password for the specified user account in Prosody]]); + return 1; + end + local user, host = arg[1]:match("([^@]+)@(.+)"); + if not user and host then + show_message [[Failed to understand JID, please supply the JID you want to set the password for]] + show_usage [[passwd user@host]] + return 1; + end + + if not host then + show_message [[Please specify a JID, including a host. e.g. alice@example.com]]; + return 1; + end + + if not prosodyctl.user_exists { user = user, host = host } then + show_message [[That user does not exist, use prosodyctl adduser to create a new user]] + return 1; + end + + local password = read_password(); + if not password then return 1; end + + local ok, msg = prosodyctl.passwd { user = user, host = host, password = password }; + + if ok then return 0; end + + show_message(error_messages[msg]) + return 1; +end + +function commands.deluser(arg) + if not arg[1] or arg[1] == "--help" then + show_usage([[deluser JID]], [[Permanently remove the specified user account from Prosody]]); + return 1; + end + local user, host = arg[1]:match("([^@]+)@(.+)"); + if not user and host then + show_message [[Failed to understand JID, please supply the JID you want to set the password for]] + show_usage [[passwd user@host]] + return 1; + end + + if not host then + show_message [[Please specify a JID, including a host. e.g. alice@example.com]]; + return 1; + end + + if not prosodyctl.user_exists { user = user, host = host } then + show_message [[That user does not exist on this server]] + return 1; + end + + local ok, msg = prosodyctl.passwd { user = user, host = host }; + + if ok then return 0; end + + show_message(error_messages[msg]) + return 1; +end + +function commands.start() + local ok, ret = prosodyctl.isrunning(); + if not ok then + show_message(error_messages[ret]); + return 1; + end + + if ret then + local ok, ret = prosodyctl.getpid(); + if not ok then + show_message("Couldn't get running Prosody's PID"); + show_message(error_messages[ret]); + return 1; + end + show_message("Prosody is already running with PID %s", ret or "(unknown)"); + return 1; + end + + local ok, ret = prosodyctl.start(); + if ok then return 0; end + + show_message("Failed to start Prosody"); + show_message(error_messages[ret]) + return 1; +end + +function commands.status() + local ok, ret = prosodyctl.isrunning(); + if not ok then + show_message(error_messages[ret]); + return 1; + end + + if ret then + local ok, ret = prosodyctl.getpid(); + if not ok then + show_message("Couldn't get running Prosody's PID"); + show_message(error_messages[ret]); + return 1; + end + show_message("Prosody is running with PID %s", ret or "(unknown)"); + return 0; + end + return 1; +end + +function commands.stop() + if not prosodyctl.isrunning() then + show_message("Prosody is not running"); + return 1; + end + + local ok, ret = prosodyctl.stop(); + if ok then return 0; end + + show_message(error_messages[ret]) + return 1; +end + +-- ejabberdctl compatibility + +function commands.register(arg) + local user, host, password = unpack(arg); + if (not (user and host)) or arg[1] == "--help" then + if not user and user ~= "--help" then + show_message [[No username specified]] + elseif not host then + show_message [[Please specify which host you want to register the user on]]; + end + show_usage("register USER HOST [PASSWORD]", "Register a user on the server, with the given password"); + return 1; + end + if not password then + password = read_password(); + if not password then + show_message [[Unable to register user with no password]]; + return 1; + end + end + + local ok, msg = prosodyctl.adduser { user = user, host = host, password = password }; + + if ok then return 0; end + + show_message(error_messages[msg]) + return 1; +end + +function commands.unregister(arg) + local user, host = unpack(arg); + if (not (user and host)) or arg[1] == "--help" then + if not user then + show_message [[No username specified]] + elseif not host then + show_message [[Please specify which host you want to unregister the user from]]; + end + show_usage("register USER HOST [PASSWORD]", "Permanently remove a user account from the server"); + return 1; + end + + local ok, msg = prosodyctl.deluser { user = user, host = host }; + + if ok then return 0; end + + show_message(error_messages[msg]) + return 1; +end + + +--------------------- + +if not commands[command] then -- Show help for all commands + function show_usage(usage, desc) + print(" "..usage); + print(" "..desc); + end + + print("prosodyctl - Manage a Prosody server"); + print(""); + print("Usage: "..arg[0].." COMMAND [OPTIONS]"); + print(""); + print("Where COMMAND may be one of:\n"); + + local commands_order = { "adduser", "passwd", "deluser" }; + + local done = {}; + + for _, command_name in ipairs(commands_order) do + local command = commands[command_name]; + if command then + command{ "--help" }; + print"" + done[command_name] = true; + end + end + + for command_name, command in pairs(commands) do + if not done[command_name] then + command{ "--help" }; + print"" + done[command_name] = true; + end + end + + + os.exit(0); +end + +os.exit(commands[command]({ select(2, unpack(arg)) })); diff --git a/util/prosodyctl.lua b/util/prosodyctl.lua new file mode 100644 index 00000000..4c6b6ea4 --- /dev/null +++ b/util/prosodyctl.lua @@ -0,0 +1,113 @@ + +local config = require "core.configmanager"; +local encodings = require "util.encodings"; +local stringprep = encodings.stringprep; +local usermanager = require "core.usermanager"; +local signal = require "util.signal"; + +local nodeprep, nameprep = stringprep.nodeprep, stringprep.nameprep; + +local io, os = io, os; +local tostring, tonumber = tostring, tonumber; +module "prosodyctl" + +function adduser(params) + local user, host, password = nodeprep(params.user), nameprep(params.host), params.password; + if not user then + return false, "invalid-username"; + elseif not host then + return false, "invalid-hostname"; + end + + usermanager.create_user(user, password, host); + return true; +end + +function user_exists(params) + return usermanager.user_exists(params.user, params.host); +end + +function passwd(params) + if not _M.user_exists(params) then + return false, "no-such-user"; + end + + return _M.adduser(params); +end + +function deluser(params) + if not _M.user_exists(params) then + return false, "no-such-user"; + end + params.password = nil; + + return _M.adduser(params); +end + +function getpid() + local pidfile = config.get("*", "core", "pidfile"); + if not pidfile then + return false, "no-pidfile"; + end + + local file, err = io.open(pidfile); + if not file then + return false, "pidfile-read-failed", ret; + end + + local pid = tonumber(file:read("*a")); + file:close(); + + if not pid then + return false, "invalid-pid"; + end + + return true, pid; +end + +function isrunning() + local ok, pid, err = _M.getpid(); + if not ok then + if pid == "pidfile-read-failed" then + -- Report as not running, since we can't open the pidfile + -- (it probably doesn't exist) + return true, false; + end + return ok, pid; + end + return true, signal.kill(pid, 0) == 0; +end + +function start() + local ok, ret = _M.isrunning(); + if not ok then + return ok, ret; + end + if ret then + return false, "already-running"; + end + if not CFG_SOURCEDIR then + os.execute("./prosody"); + elseif CFG_SOURCEDIR:match("^/usr/local") then + os.execute("/usr/local/bin/prosody"); + else + os.execute("prosody"); + end + return true; +end + +function stop() + local ok, ret = _M.isrunning(); + if not ok then + return ok, ret; + end + if not ret then + return false, "not-running"; + end + + local ok, pid = _M.getpid() + if not ok then return false, pid; end + + signal.kill(pid, signal.SIGTERM); + return true; +end -- cgit v1.2.3