Created
March 20, 2010 02:41
-
-
Save zeen/338421 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
local st, uuid = require "util.stanza", require "util.uuid"; | |
local xmlns_cmd = "http://jabber.org/protocol/commands"; | |
local states = {} | |
local _M = {}; | |
function _cmdtag(desc, status, sessionid, action) | |
local cmd = st.stanza("command", { xmlns = xmlns_cmd, node = desc.node, status = status }); | |
if sessionid then cmd.attr.sessionid = sessionid; end | |
if action then cmd.attr.action = action; end | |
return cmd; | |
end | |
function _M.new(name, node, handler, permission) | |
return { name = name, node = node, handler = handler, cmdtag = _cmdtag, permission = (permission or "user") }; | |
end | |
function _M.handle_cmd(command, origin, stanza) | |
local sessionid = stanza.tags[1].attr.sessionid or uuid.generate(); | |
local dataIn = {}; | |
dataIn.to = stanza.attr.to; | |
dataIn.from = stanza.attr.from; | |
dataIn.action = stanza.tags[1].attr.action or nil; | |
dataIn.form = stanza.tags[1]:child_with_ns("jabber:x:data"); | |
local data, state = command:handler(dataIn, states[sessionid]); | |
states[sessionid] = state; | |
local stanza = st.reply(stanza); | |
if data.status == "completed" then | |
states[sessionid] = nil; | |
cmdtag = command:cmdtag("completed", sessionid); | |
elseif data.status == "canceled" then | |
states[sessionid] = nil; | |
cmdtag = command:cmdtag("canceled", sessionid); | |
elseif data.status == "error" then | |
states[sessionid] = nil; | |
stanza = st.error_reply(stanza, data.error.type, data.error.condition, data.error.message); | |
cmdtag = command:cmdtag("canceled", sessionid); | |
else | |
cmdtag = command:cmdtag("executing", sessionid); | |
end | |
for name, content in pairs(data) do | |
if name == "info" then | |
cmdtag:tag("note", {type="info"}):text(content):up(); | |
elseif name == "warn" then | |
cmdtag:tag("note", {type="warn"}):text(content):up(); | |
elseif name == "error" then | |
cmdtag:tag("note", {type="error"}):text(content.message):up(); | |
elseif name =="actions" then | |
local actions = st.stanza("actions"); | |
for _, action in ipairs(content) do | |
if (action == "prev") or (action == "next") or (action == "complete") then | |
actions:tag(action):up(); | |
else | |
module:log("error", 'Command "'..command.name.. | |
'" at node "'..command.node..'" provided an invalid action "'..action..'"'); | |
end | |
end | |
cmdtag:add_child(actions); | |
elseif name == "form" then | |
cmdtag:add_child(content:form()); | |
elseif name == "result" then | |
cmdtag:add_child(content.layout:form(content.data, "result")); | |
elseif name == "other" then | |
cmdtag:add_child(content); | |
end | |
end | |
stanza:add_child(cmdtag); | |
origin.send(stanza); | |
return true; | |
end | |
return _M; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- Copyright (C) 2009 Thilo Cestonaro | |
-- | |
-- This file is MIT/X11 licensed. Please see the | |
-- COPYING file in the source package for more information. | |
-- | |
local st = require "util.stanza"; | |
local is_admin = require "core.usermanager".is_admin; | |
local adhoc_handle_cmd = module:require "adhoc".handle_cmd; | |
local xmlns_cmd = "http://jabber.org/protocol/commands"; | |
local xmlns_disco = "http://jabber.org/protocol/disco"; | |
local commands = {}; | |
module:add_feature(xmlns_cmd); | |
module:hook("iq/host/"..xmlns_disco.."#items:query", function (event) | |
local origin, stanza = event.origin, event.stanza; | |
-- TODO: Is this correct, or should is_admin be changed? | |
local privileged = is_admin(stanza.attr.from) | |
or is_admin(stanza.attr.from, stanza.attr.to); | |
if stanza.attr.type == "get" and stanza.tags[1].attr.node | |
and stanza.tags[1].attr.node == xmlns_cmd then | |
reply = st.reply(stanza); | |
reply:tag("query", { xmlns = xmlns_disco.."#items", | |
node = xmlns_cmd }); | |
for node, command in pairs(commands) do | |
if (command.permission == "admin" and privileged) | |
or (command.permission == "user") then | |
reply:tag("item", { name = command.name, | |
node = node, jid = module:get_host() }); | |
reply:up(); | |
end | |
end | |
origin.send(reply); | |
return true; | |
end | |
end, 500); | |
module:hook("iq/host", function (event) | |
local origin, stanza = event.origin, event.stanza; | |
if stanza.attr.type == "set" and stanza.tags[1] | |
and stanza.tags[1].name == "command" then | |
local node = stanza.tags[1].attr.node | |
-- TODO: Is this correct, or should is_admin be changed? | |
local privileged = is_admin(event.stanza.attr.from) | |
or is_admin(stanza.attr.from, stanza.attr.to); | |
if commands[node] then | |
if commands[node].permission == "admin" | |
and not privileged then | |
origin.send(st.error_reply(stanza, "auth", "forbidden", "You don't have permission to execute this command"):up() | |
:add_child(commands[node]:cmdtag("canceled") | |
:tag("note", {type="error"}):text("You don't have permission to execute this command"))); | |
return true | |
end | |
-- User has permission now execute the command | |
return adhoc_handle_cmd(commands[node], origin, stanza); | |
end | |
end | |
end, 500); | |
module:hook("item-added/adhoc", function (event) | |
commands[event.item.node] = event.item; | |
end, 500); | |
module:hook("item-removed/adhoc", function (event) | |
commands[event.item.node] = nil; | |
end, 500); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- Copyright (C) 2009 Florian Zeitz | |
-- | |
-- This file is MIT/X11 licensed. Please see the | |
-- COPYING file in the source package for more information. | |
-- | |
local _G = _G; | |
local prosody = _G.prosody; | |
local hosts = prosody.hosts; | |
local t_concat = table.concat; | |
local usermanager_user_exists = require "core.usermanager".user_exists; | |
local usermanager_get_password = require "core.usermanager".get_password; | |
local usermanager_create_user = require "core.usermanager".create_user; | |
local is_admin = require "core.usermanager".is_admin; | |
local rm_load_roster = require "core.rostermanager".load_roster; | |
local st, jid, uuid = require "util.stanza", require "util.jid", require "util.uuid"; | |
local timer_add_task = require "util.timer".add_task; | |
local dataforms_new = require "util.dataforms".new; | |
module:log("debug", module:get_name()); | |
local adhoc_new = module:require "adhoc".new; | |
local add_user_layout = dataforms_new{ | |
title = "Adding a User"; | |
instructions = "Fill out this form to add a user."; | |
{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; | |
{ name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for the account to be added" }; | |
{ name = "password", type = "text-private", label = "The password for this account" }; | |
{ name = "password-verify", type = "text-private", label = "Retype password" }; | |
}; | |
local change_user_password_layout = dataforms_new{ | |
title = "Changing a User Password"; | |
instructions = "Fill out this form to change a user's password."; | |
{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; | |
{ name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for this account" }; | |
{ name = "password", type = "text-private", required = true, label = "The password for this account" }; | |
}; | |
local delete_user_layout = dataforms_new{ | |
title = "Deleting a User"; | |
instructions = "Fill out this form to delete a user."; | |
{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; | |
{ name = "accountjids", type = "jid-multi", label = "The Jabber ID(s) to delete" }; | |
}; | |
local end_user_session_layout = dataforms_new{ | |
title = "Ending a User Session"; | |
instructions = "Fill out this form to end a user's session."; | |
{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; | |
{ name = "accountjids", type = "jid-multi", label = "The Jabber ID(s) for which to end sessions" }; | |
}; | |
local get_user_password_layout = dataforms_new{ | |
title = "Getting User's Password"; | |
instructions = "Fill out this form to get a user's password."; | |
{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; | |
{ name = "accountjid", type = "jid-single", label = "The Jabber ID for which to retrieve the password" }; | |
}; | |
local get_user_password_result_layout = dataforms_new{ | |
{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; | |
{ name = "accountjid", type = "jid-single", label = "JID" }; | |
{ name = "password", type = "text-single", label = "Password" }; | |
}; | |
local get_user_roster_layout = dataforms_new{ | |
{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; | |
{ name = "accountjid", type = "jid-single", label = "The Jabber ID for which to retrieve the roster" }; | |
}; | |
local get_user_roster_result_layout = dataforms_new{ | |
{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; | |
{ name = "accountjid", type = "jid-single", label = "This is the roster for" }; | |
{ name = "roster", type = "text-multi", label = "Roster XML" }; | |
}; | |
local get_online_users_layout = dataforms_new{ | |
title = "Getting List of Online Users"; | |
instructions = "How many users should be returned at most?"; | |
{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; | |
{ name = "max_items", type = "list-single", label = "Maximum number of users", | |
value = { "25", "50", "75", "100", "150", "200", "all" } }; | |
}; | |
local get_online_users_result_layout = dataforms_new{ | |
{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; | |
{ name = "onlineuserjids", type = "text-multi", label = "The list of all online users" }; | |
}; | |
local announce_layout = dataforms_new{ | |
title = "Making an Announcement"; | |
instructions = "Fill out this form to make an announcement to all\nactive users of this service."; | |
{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; | |
{ name = "subject", type = "text-single", label = "Subject" }; | |
{ name = "announcement", type = "text-multi", required = true, label = "Announcement" }; | |
}; | |
local shut_down_service_layout = dataforms_new{ | |
title = "Shutting Down the Service"; | |
instructions = "Fill out this form to shut down the service."; | |
{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; | |
{ name = "delay", type = "list-single", label = "Time delay before shutting down", | |
value = { {label = "30 seconds", value = "30"}, | |
{label = "60 seconds", value = "60"}, | |
{label = "90 seconds", value = "90"}, | |
{label = "2 minutes", value = "120"}, | |
{label = "3 minutes", value = "180"}, | |
{label = "4 minutes", value = "240"}, | |
{label = "5 minutes", value = "300"}, | |
}; | |
}; | |
{ name = "announcement", type = "text-multi", label = "Announcement" }; | |
}; | |
function add_user_command_handler(self, data, state) | |
if state then | |
if data.action == "cancel" then | |
return { status = "canceled" }; | |
end | |
local fields = add_user_layout:data(data.form); | |
local username, host, resource = jid.split(fields.accountjid); | |
if (fields["password"] == fields["password-verify"]) and username and host and host == data.to then | |
if usermanager_user_exists(username, host) then | |
return { status = "error", error = { type = "cancel", condition = "conflict", message = "Account already exists" } }; | |
else | |
if usermanager_create_user(username, fields.password, host) then | |
module:log("info", "Created new account " .. username.."@"..host); | |
return { status = "completed", info = "Account successfully created" }; | |
else | |
return { status = "error", error = { type = "wait", condition = "internal-server-error", | |
message = "Failed to write data to disk" } }; | |
end | |
end | |
else | |
module:log("debug", fields.accountjid .. " " .. fields.password .. " " .. fields["password-verify"]); | |
return { status = "error", error = { type = "cancel", condition = "conflict", | |
message = "Invalid data.\nPassword mismatch, or empty username" } }; | |
end | |
else | |
return { status = "executing", form = add_user_layout }, "executing"; | |
end | |
end | |
function change_user_password_command_handler(self, data, state) | |
if state then | |
if data.action == "cancel" then | |
return { status = "canceled" }; | |
end | |
local fields = change_user_password_layout:data(data.form); | |
local username, host, resource = jid.split(fields.accountjid); | |
if usermanager_user_exists(username, host) and usermanager_create_user(username, fields.password, host) then | |
return { status = "completed", info = "Password successfully changed" }; | |
else | |
return { status = "error", error = { type = "cancel", condition = "item-not-found", message = "User does not exist" } }; | |
end | |
else | |
return { status = "executing", form = change_user_password_layout }, "executing"; | |
end | |
end | |
function disconnect_user(match_jid) | |
local node, hostname, givenResource = jid.split(match_jid); | |
local host = hosts[hostname]; | |
local sessions = host.sessions[node] and host.sessions[node].sessions; | |
for resource, session in pairs(sessions or {}) do | |
if not givenResource or (resource == givenResource) then | |
module:log("debug", "Disconnecting "..node.."@"..hostname.."/"..resource); | |
session:close(); | |
end | |
end | |
return true; | |
end | |
function delete_user_command_handler(self, data, state) | |
if state then | |
if data.action == "cancel" then | |
return { status = "canceled" }; | |
end | |
local fields = delete_user_layout:data(data.form); | |
local failed = {}; | |
local succeeded = {}; | |
for _, aJID in ipairs(fields.accountjids) do | |
local username, host, resource = jid.split(aJID); | |
if usermanager_user_exists(username, host) and disconnect_user(aJID) and usermanager_create_user(username, nil, host) then | |
module:log("debug", "User " .. aJID .. " has been deleted"); | |
succeeded[#succeeded+1] = aJID; | |
else | |
module:log("debug", "Tried to delete non-existant user "..aJID); | |
failed[#failed+1] = aJID; | |
end | |
end | |
return {status = "completed", info = (#succeeded ~= 0 and | |
"The following accounts were successfully deleted:\n"..t_concat(succeeded, "\n").."\n" or "").. | |
(#failed ~= 0 and | |
"The following accounts could not be deleted:\n"..t_concat(failed, "\n") or "") }; | |
else | |
return { status = "executing", form = delete_user_layout }, "executing"; | |
end | |
end | |
function end_user_session_handler(self, data, state) | |
if state then | |
if data.action == "cancel" then | |
return { status = "canceled" }; | |
end | |
local fields = end_user_session_layout:data(data.form); | |
for _, aJID in ipairs(fields.accountjids) do | |
disconnect_user(aJID); | |
end | |
return { status = "completed", info = "User(s) have been disconnected" }; | |
else | |
return { status = "executing", form = end_user_session_layout }, "executing"; | |
end | |
end | |
function get_user_password_handler(self, data, state) | |
if state then | |
if data.action == "cancel" then | |
return { status = "canceled" }; | |
end | |
local fields = get_user_password_layout:data(data.form); | |
local user, host, resource = jid.split(fields.accountjid); | |
local accountjid = ""; | |
local password = ""; | |
if usermanager_user_exists(user, host) then | |
accountjid = fields.accountjid; | |
password = usermanager_get_password(user, host); | |
else | |
return { status = "error", error = { type = "cancel", condition = "item-not-found", message = "User does not exist" } }; | |
end | |
return { status = "completed", result = { layout = get_user_password_result_layout, data = {accountjid = accountjid, password = password} } }; | |
else | |
return { status = "executing", form = get_user_password_layout }, "executing"; | |
end | |
end | |
function get_user_roster_handler(self, data, state) | |
if state then | |
if data.action == "cancel" then | |
return { status = "canceled" }; | |
end | |
local fields = add_user_layout:data(data.form); | |
local user, host, resource = jid.split(fields.accountjid); | |
if not usermanager_user_exists(user, host) then | |
return { status = "error", error = { type = "cancel", condition = "item-not-found", message = "User does not exist" } }; | |
end | |
local roster = rm_load_roster(user, host); | |
local query = st.stanza("query", { xmlns = "jabber:iq:roster" }); | |
for jid in pairs(roster) do | |
if jid ~= "pending" and jid then | |
query:tag("item", { | |
jid = jid, | |
subscription = roster[jid].subscription, | |
ask = roster[jid].ask, | |
name = roster[jid].name, | |
}); | |
for group in pairs(roster[jid].groups) do | |
query:tag("group"):text(group):up(); | |
end | |
query:up(); | |
end | |
end | |
local query_text = query:__tostring(); -- TODO: Use upcoming pretty_print() function | |
query_text = query_text:gsub("><", ">\n<"); | |
local result = get_user_roster_result_layout:form({ accountjid = user.."@"..host, roster = query_text }, "result"); | |
result:add_child(query); | |
return { status = "completed", other = result }; | |
else | |
return { status = "executing", form = get_user_roster_layout }, "executing"; | |
end | |
end | |
function get_online_users_command_handler(self, data, state) | |
if state then | |
if data.action == "cancel" then | |
return { status = "canceled" }; | |
end | |
local fields = add_user_layout:data(data.form); | |
local max_items = nil | |
if fields.max_items ~= "all" then | |
max_items = tonumber(fields.max_items); | |
end | |
local count = 0; | |
local users = nil; | |
for username, user in pairs(hosts[data.to].sessions or {}) do | |
if (max_items ~= nil) and (count >= max_items) then | |
break; | |
end | |
users = ((users and users.."\n") or "")..(username.."@"..data.to); | |
count = count + 1; | |
end | |
return { status = "completed", result = {layout = get_online_users_result_layout, data = {onlineuserjids=users}} }; | |
else | |
return { status = "executing", form = get_online_users_layout }, "executing"; | |
end | |
end | |
function send_to_online(message, server) | |
if server then | |
sessions = { [server] = hosts[server] }; | |
else | |
sessions = hosts; | |
end | |
local c = 0; | |
for domain, session in pairs(sessions) do | |
for user in pairs(session.sessions or {}) do | |
c = c + 1; | |
message.attr.from = domain; | |
message.attr.to = user.."@"..domain; | |
core_post_stanza(session, message); | |
end | |
end | |
return c; | |
end | |
function announce_handler(self, data, state) | |
if state then | |
if data.action == "cancel" then | |
return { status = "canceled" }; | |
end | |
local fields = announce_layout:data(data.form); | |
module:log("info", "Sending server announcement to all online users"); | |
local message = st.message({type = "headline"}, fields.announcement):up() | |
:tag("subject"):text(fields.subject or "Announcement"); | |
local count = send_to_online(message, data.to); | |
module:log("info", "Announcement sent to %d online users", count); | |
return { status = "completed", info = "Announcement sent." }; | |
else | |
return { status = "executing", form = announce_layout }, "executing"; | |
end | |
return true; | |
end | |
function shut_down_service_handler(self, data, state) | |
if state then | |
if data.action == "cancel" then | |
return { status = "canceled" }; | |
end | |
local fields = shut_down_service_layout:data(data.form); | |
if fields.announcement then | |
local message = st.message({type = "headline"}, fields.announcement):up() | |
:tag("subject"):text("Server is shutting down"); | |
send_to_online(message); | |
end | |
timer_add_task(tonumber(fields.delay or "5"), prosody.shutdown); | |
return { status = "completed", info = "Server is about to shut down" }; | |
else | |
return { status = "executing", form = shut_down_service_layout }, "executing"; | |
end | |
return true; | |
end | |
local add_user_desc = adhoc_new("Add User", "http://jabber.org/protocol/admin#add-user", add_user_command_handler, "admin"); | |
local announce_desc = adhoc_new("Send Announcement to Online Users", "http://jabber.org/protocol/admin#announce", announce_handler, "admin"); | |
local change_user_password_desc = adhoc_new("Change User Password", "http://jabber.org/protocol/admin#change-user-password", change_user_password_command_handler, "admin"); | |
local delete_user_desc = adhoc_new("Delete User", "http://jabber.org/protocol/admin#delete-user", delete_user_command_handler, "admin"); | |
local end_user_session_desc = adhoc_new("End User Session", "http://jabber.org/protocol/admin#end-user-session", end_user_session_handler, "admin"); | |
local get_user_password_desc = adhoc_new("Get User Password", "http://jabber.org/protocol/admin#get-user-password", get_user_password_handler, "admin"); | |
local get_user_roster_desc = adhoc_new("Get User Roster","http://jabber.org/protocol/admin#get-user-roster", get_user_roster_handler, "admin"); | |
local get_online_users_desc = adhoc_new("Get List of Online Users", "http://jabber.org/protocol/admin#get-online-users", get_online_users_command_handler, "admin"); | |
local shut_down_service_desc = adhoc_new("Shut Down Service", "http://jabber.org/protocol/admin#shutdown", shut_down_service_handler, "admin"); | |
module:add_item("adhoc", add_user_desc); | |
module:add_item("adhoc", announce_desc); | |
module:add_item("adhoc", change_user_password_desc); | |
module:add_item("adhoc", delete_user_desc); | |
module:add_item("adhoc", end_user_session_desc); | |
module:add_item("adhoc", get_user_password_desc); | |
module:add_item("adhoc", get_user_roster_desc); | |
module:add_item("adhoc", get_online_users_desc); | |
module:add_item("adhoc", shut_down_service_desc); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment