aboutsummaryrefslogtreecommitdiffstats
path: root/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'plugins')
-rw-r--r--plugins/mod_bosh.lua4
-rw-r--r--plugins/mod_console.lua8
-rw-r--r--plugins/mod_presence.lua11
-rw-r--r--plugins/mod_proxy65.lua255
-rw-r--r--plugins/mod_register.lua14
-rw-r--r--plugins/mod_roster.lua16
-rw-r--r--plugins/mod_saslauth.lua89
-rw-r--r--plugins/mod_tls.lua10
-rw-r--r--plugins/muc/muc.lib.lua143
9 files changed, 426 insertions, 124 deletions
diff --git a/plugins/mod_bosh.lua b/plugins/mod_bosh.lua
index 3e41ef7b..5de79eff 100644
--- a/plugins/mod_bosh.lua
+++ b/plugins/mod_bosh.lua
@@ -152,7 +152,7 @@ function stream_callbacks.streamopened(request, attr)
local r, send_buffer = session.requests, session.send_buffer;
local response = { headers = default_headers }
function session.send(s)
- log("debug", "Sending BOSH data: %s", tostring(s));
+ --log("debug", "Sending BOSH data: %s", tostring(s));
local oldest_request = r[1];
while oldest_request and oldest_request.destroyed do
t_remove(r, 1);
@@ -160,7 +160,7 @@ function stream_callbacks.streamopened(request, attr)
oldest_request = r[1];
end
if oldest_request then
- log("debug", "We have an open request, so using that to send with");
+ log("debug", "We have an open request, so sending on that");
response.body = t_concat{"<body xmlns='http://jabber.org/protocol/httpbind' sid='", sid, "' xmlns:stream = 'http://etherx.jabber.org/streams'>", tostring(s), "</body>" };
oldest_request:send(response);
--log("debug", "Sent");
diff --git a/plugins/mod_console.lua b/plugins/mod_console.lua
index 82045232..9962b3f6 100644
--- a/plugins/mod_console.lua
+++ b/plugins/mod_console.lua
@@ -33,11 +33,11 @@ end
console = {};
function console:new_session(conn)
- local w = function(s) conn.write(s:gsub("\n", "\r\n")); end;
+ local w = function(s) conn:write(s:gsub("\n", "\r\n")); end;
local session = { conn = conn;
send = function (t) w(tostring(t)); end;
print = function (t) w("| "..tostring(t).."\n"); end;
- disconnect = function () conn.close(); end;
+ disconnect = function () conn:close(); end;
};
session.env = setmetatable({}, default_env_mt);
@@ -53,7 +53,7 @@ end
local sessions = {};
-function console_listener.listener(conn, data)
+function console_listener.onincoming(conn, data)
local session = sessions[conn];
if not session then
@@ -126,7 +126,7 @@ function console_listener.listener(conn, data)
session.send(string.char(0));
end
-function console_listener.disconnect(conn, err)
+function console_listener.ondisconnect(conn, err)
local session = sessions[conn];
if session then
session.disconnect();
diff --git a/plugins/mod_presence.lua b/plugins/mod_presence.lua
index f83e017b..abbc3a3d 100644
--- a/plugins/mod_presence.lua
+++ b/plugins/mod_presence.lua
@@ -200,9 +200,6 @@ function handle_outbound_presence_subscriptions_and_probes(origin, stanza, from_
rostermanager.roster_push(node, host, to_bare);
end
core_route_stanza(origin, stanza);
- -- COMPAT: Some legacy clients keep displaying unsubscribed contacts as online unless an unavailable presence is sent:
- send_presence_of_available_resources(node, host, to_bare, origin, core_route_stanza,
- st.presence({ type="unavailable", from=from_bare, to=to_bare, id=stanza.attr.id }));
end
stanza.attr.from, stanza.attr.to = st_from, st_to;
end
@@ -223,16 +220,17 @@ function handle_inbound_presence_subscriptions_and_probes(origin, stanza, from_b
-- TODO send last recieved unavailable presence (or we MAY do nothing, which is fine too)
end
else
- core_route_stanza(origin, st.presence({from=to_bare, to=from_bare, type="unsubscribed"}));
+ core_route_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unsubscribed"}));
end
elseif stanza.attr.type == "subscribe" then
if rostermanager.is_contact_subscribed(node, host, from_bare) then
- core_route_stanza(origin, st.presence({from=to_bare, to=from_bare, type="subscribed"})); -- already subscribed
+ core_route_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="subscribed"})); -- already subscribed
-- Sending presence is not clearly stated in the RFC, but it seems appropriate
if 0 == send_presence_of_available_resources(node, host, from_bare, origin, core_route_stanza) then
-- TODO send last recieved unavailable presence (or we MAY do nothing, which is fine too)
end
else
+ core_route_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unavailable"})); -- acknowledging receipt
if not rostermanager.is_contact_pending_in(node, host, from_bare) then
if rostermanager.set_contact_pending_in(node, host, from_bare) then
sessionmanager.send_to_available_resources(node, host, stanza);
@@ -241,14 +239,17 @@ function handle_inbound_presence_subscriptions_and_probes(origin, stanza, from_b
end
elseif stanza.attr.type == "unsubscribe" then
if rostermanager.process_inbound_unsubscribe(node, host, from_bare) then
+ sessionmanager.send_to_interested_resources(node, host, stanza);
rostermanager.roster_push(node, host, from_bare);
end
elseif stanza.attr.type == "subscribed" then
if rostermanager.process_inbound_subscription_approval(node, host, from_bare) then
+ sessionmanager.send_to_interested_resources(node, host, stanza);
rostermanager.roster_push(node, host, from_bare);
end
elseif stanza.attr.type == "unsubscribed" then
if rostermanager.process_inbound_subscription_cancellation(node, host, from_bare) then
+ sessionmanager.send_to_interested_resources(node, host, stanza);
rostermanager.roster_push(node, host, from_bare);
end
end -- discard any other type
diff --git a/plugins/mod_proxy65.lua b/plugins/mod_proxy65.lua
new file mode 100644
index 00000000..504cfc0c
--- /dev/null
+++ b/plugins/mod_proxy65.lua
@@ -0,0 +1,255 @@
+-- Copyright (C) 2009 Thilo Cestonaro
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+--[[
+* to restart the proxy in the console: e.g.
+module:unload("proxy65");
+> server.removeserver(<proxy65_port>);
+module:load("proxy65", <proxy65_jid>);
+]]--
+
+if module:get_host_type() ~= "component" then
+ error("proxy65 should be loaded as a component, please see http://prosody.im/doc/components", 0);
+end
+
+local jid_split, jid_join = require "util.jid".split, require "util.jid".join;
+local st = require "util.stanza";
+local componentmanager = require "core.componentmanager";
+local config_get = require "core.configmanager".get;
+local connlisteners = require "net.connlisteners";
+local sha1 = require "util.hashes".sha1;
+
+local host, name = module:get_host(), "SOCKS5 Bytestreams Service";
+local sessions, transfers, component, replies_cache = {}, {}, nil, {};
+
+local proxy_port = config_get(host, "core", "proxy65_port") or 5000;
+local proxy_interface = config_get(host, "core", "proxy65_interface") or "*";
+local proxy_address = config_get(host, "core", "proxy65_address") or (proxy_interface ~= "*" and proxy_interface) or host;
+local proxy_acl = config_get(host, "core", "proxy65_acl");
+
+local connlistener = { default_port = proxy_port, default_interface = proxy_interface, default_mode = "*a" };
+
+function connlistener.onincoming(conn, data)
+ local session = sessions[conn] or {};
+
+ if session.setup == nil and data ~= nil and data:sub(1):byte() == 0x05 and data:len() > 2 then
+ local nmethods = data:sub(2):byte();
+ local methods = data:sub(3);
+ local supported = false;
+ for i=1, nmethods, 1 do
+ if(methods:sub(i):byte() == 0x00) then -- 0x00 == method: NO AUTH
+ supported = true;
+ break;
+ end
+ end
+ if(supported) then
+ module:log("debug", "new session found ... ")
+ session.setup = true;
+ sessions[conn] = session;
+ conn:write(string.char(5, 0));
+ end
+ return;
+ end
+ if session.setup then
+ if session.sha ~= nil and transfers[session.sha] ~= nil then
+ local sha = session.sha;
+ if transfers[sha].activated == true and transfers[sha].initiator == conn and transfers[sha].target ~= nil then
+ transfers[sha].target:write(data);
+ return;
+ end
+ end
+ if data ~= nil and data:len() == 0x2F and -- 40 == length of SHA1 HASH, and 7 other bytes => 47 => 0x2F
+ data:sub(1):byte() == 0x05 and -- SOCKS5 has 5 in first byte
+ data:sub(2):byte() == 0x01 and -- CMD must be 1
+ data:sub(3):byte() == 0x00 and -- RSV must be 0
+ data:sub(4):byte() == 0x03 and -- ATYP must be 3
+ data:sub(5):byte() == 40 and -- SHA1 HASH length must be 40 (0x28)
+ data:sub(-2):byte() == 0x00 and -- PORT must be 0, size 2 byte
+ data:sub(-1):byte() == 0x00
+ then
+ local sha = data:sub(6, 45); -- second param is not count! it's the ending index (included!)
+ if transfers[sha] == nil then
+ transfers[sha] = {};
+ transfers[sha].activated = false;
+ transfers[sha].target = conn;
+ session.sha = sha;
+ module:log("debug", "target connected ... ");
+ elseif transfers[sha].target ~= nil then
+ transfers[sha].initiator = conn;
+ session.sha = sha;
+ module:log("debug", "initiator connected ... ");
+ end
+ conn:write(string.char(5, 0, 0, 3, sha:len()) .. sha .. string.char(0, 0)); -- VER, REP, RSV, ATYP, BND.ADDR (sha), BND.PORT (2 Byte)
+ else
+ log:module("warn", "Neither data transfer nor initial connect of a participator of a transfer.")
+ conn.close();
+ end
+ else
+ if data ~= nil then
+ module:log("warn", "unknown connection with no authentication data -> closing it");
+ conn.close();
+ end
+ end
+end
+
+function connlistener.ondisconnect(conn, err)
+ local session = sessions[conn];
+ if session then
+ if session.sha and transfers[session.sha] then
+ local initiator, target = transfers[session.sha].initiator, transfers[session.sha].target;
+ if initiator == conn and target ~= nil then
+ target.close();
+ elseif target == conn and initiator ~= nil then
+ initiator.close();
+ end
+ transfers[session.sha] = nil;
+ end
+ -- Clean up any session-related stuff here
+ sessions[conn] = nil;
+ end
+end
+
+local function get_disco_info(stanza)
+ local reply = replies_cache.disco_info;
+ if reply == nil then
+ reply = st.iq({type='result', from=host}):query("http://jabber.org/protocol/disco#info")
+ :tag("identity", {category='proxy', type='bytestreams', name=name}):up()
+ :tag("feature", {var="http://jabber.org/protocol/bytestreams"});
+ replies_cache.disco_info = reply;
+ end
+
+ reply.attr.id = stanza.attr.id;
+ reply.attr.to = stanza.attr.from;
+ return reply;
+end
+
+local function get_disco_items(stanza)
+ local reply = replies_cache.disco_items;
+ if reply == nil then
+ reply = st.iq({type='result', from=host}):query("http://jabber.org/protocol/disco#items");
+ replies_cache.disco_items = reply;
+ end
+
+ reply.attr.id = stanza.attr.id;
+ reply.attr.to = stanza.attr.from;
+ return reply;
+end
+
+local function get_stream_host(origin, stanza)
+ local reply = replies_cache.stream_host;
+ local err_reply = replies_cache.stream_host_err;
+ local sid = stanza.tags[1].attr.sid;
+ local allow = false;
+ local jid_node, jid_host, jid_resource = jid_split(stanza.attr.from);
+
+ if stanza.attr.from == nil then
+ jid_node = origin.username;
+ jid_host = origin.host;
+ jid_resource = origin.resource;
+ end
+
+ if proxy_acl and #proxy_acl > 0 then
+ if host ~= nil then -- at least a domain is needed.
+ for _, acl in ipairs(proxy_acl) do
+ local acl_node, acl_host, acl_resource = jid_split(acl);
+ if ((acl_node ~= nil and acl_node == jid_node) or acl_node == nil) and
+ ((acl_host ~= nil and acl_host == jid_host) or acl_host == nil) and
+ ((acl_resource ~= nil and acl_resource == jid_resource) or acl_resource == nil) then
+ allow = true;
+ end
+ end
+ end
+ else
+ allow = true;
+ end
+ if allow == true then
+ if reply == nil then
+ reply = st.iq({type="result", from=host})
+ :query("http://jabber.org/protocol/bytestreams")
+ :tag("streamhost", {jid=host, host=proxy_address, port=proxy_port});
+ replies_cache.stream_host = reply;
+ end
+ else
+ module:log("warn", "Denying use of proxy for %s", tostring(jid_join(jid_node, jid_host, jid_resource)));
+ if err_reply == nil then
+ err_reply = st.iq({type="error", from=host})
+ :query("http://jabber.org/protocol/bytestreams")
+ :tag("error", {code='403', type='auth'})
+ :tag("forbidden", {xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'});
+ replies_cache.stream_host_err = err_reply;
+ end
+ reply = err_reply;
+ end
+ reply.attr.id = stanza.attr.id;
+ reply.attr.to = stanza.attr.from;
+ reply.tags[1].attr.sid = sid;
+ return reply;
+end
+
+module.unload = function()
+ componentmanager.deregister_component(host);
+ connlisteners.deregister(module.host .. ':proxy65');
+end
+
+local function set_activation(stanza)
+ local from, to, sid, reply = nil;
+ from = stanza.attr.from;
+ if stanza.tags[1] ~= nil and tostring(stanza.tags[1].name) == "query" then
+ if stanza.tags[1].attr ~= nil then
+ sid = stanza.tags[1].attr.sid;
+ end
+ if stanza.tags[1].tags[1] ~= nil and tostring(stanza.tags[1].tags[1].name) == "activate" then
+ to = stanza.tags[1].tags[1][1];
+ end
+ end
+ if from ~= nil and to ~= nil and sid ~= nil then
+ reply = st.iq({type="result", from=host, to=from});
+ reply.attr.id = stanza.attr.id;
+ end
+ return reply, from, to, sid;
+end
+
+function handle_to_domain(origin, stanza)
+ local to_node, to_host, to_resource = jid_split(stanza.attr.to);
+ if to_node == nil then
+ local type = stanza.attr.type;
+ if type == "error" or type == "result" then return; end
+ if stanza.name == "iq" and type == "get" then
+ local xmlns = stanza.tags[1].attr.xmlns
+ if xmlns == "http://jabber.org/protocol/disco#info" then
+ origin.send(get_disco_info(stanza));
+ return true;
+ elseif xmlns == "http://jabber.org/protocol/disco#items" then
+ origin.send(get_disco_items(stanza));
+ return true;
+ elseif xmlns == "http://jabber.org/protocol/bytestreams" then
+ origin.send(get_stream_host(origin, stanza));
+ return true;
+ end
+ elseif stanza.name == "iq" and type == "set" then
+ local reply, from, to, sid = set_activation(stanza);
+ if reply ~= nil and from ~= nil and to ~= nil and sid ~= nil then
+ local sha = sha1(sid .. from .. to, true);
+ if transfers[sha] == nil then
+ module:log("error", "transfers[sha]: nil");
+ elseif(transfers[sha] ~= nil and transfers[sha].initiator ~= nil and transfers[sha].target ~= nil) then
+ origin.send(reply);
+ transfers[sha].activated = true;
+ end
+ else
+ module:log("error", "activation failed: sid: %s, initiator: %s, target: %s", tostring(sid), tostring(from), tostring(to));
+ end
+ end
+ end
+ return;
+end
+
+if not connlisteners.register(module.host .. ':proxy65', connlistener) then
+ error("mod_proxy65: Could not establish a connection listener. Check your configuration please.");
+ error(" one possible cause for this would be that two proxy65 components share the same port.");
+end
+
+connlisteners.start(module.host .. ':proxy65');
+component = componentmanager.register_component(host, handle_to_domain);
diff --git a/plugins/mod_register.lua b/plugins/mod_register.lua
index 2ae01fed..bda40124 100644
--- a/plugins/mod_register.lua
+++ b/plugins/mod_register.lua
@@ -43,21 +43,21 @@ module:add_iq_handler("c2s", "jabber:iq:register", function (session, stanza)
session:close({condition = "not-authorized", text = "Account deleted"});
end
-- TODO datamanager should be able to delete all user data itself
- datamanager.store(username, host, "roster", nil);
datamanager.store(username, host, "vcard", nil);
datamanager.store(username, host, "private", nil);
datamanager.store(username, host, "offline", nil);
- --local bare = username.."@"..host;
+ local bare = username.."@"..host;
for jid, item in pairs(roster) do
- if jid ~= "pending" then
- if item.subscription == "both" or item.subscription == "to" then
- -- TODO unsubscribe
+ if jid and jid ~= "pending" then
+ if item.subscription == "both" or item.subscription == "from" or (roster.pending and roster.pending[jid]) then
+ core_post_stanza(hosts[host], st.presence({type="unsubscribed", from=bare, to=jid}));
end
- if item.subscription == "both" or item.subscription == "from" then
- -- TODO unsubscribe
+ if item.subscription == "both" or item.subscription == "to" or item.ask then
+ core_post_stanza(hosts[host], st.presence({type="unsubscribe", from=bare, to=jid}));
end
end
end
+ datamanager.store(username, host, "roster", nil);
datamanager.store(username, host, "accounts", nil); -- delete accounts datastore at the end
module:log("info", "User removed their account: %s@%s", username, host);
module:fire_event("user-deregistered", { username = username, host = host, source = "mod_register", session = session });
diff --git a/plugins/mod_roster.lua b/plugins/mod_roster.lua
index 7ca22aa1..52c61a26 100644
--- a/plugins/mod_roster.lua
+++ b/plugins/mod_roster.lua
@@ -74,18 +74,20 @@ module:add_iq_handler("c2s", "jabber:iq:roster",
if not resource and host then
if jid ~= from_node.."@"..from_host then
if item.attr.subscription == "remove" then
- local r_item = session.roster[jid];
+ local roster = session.roster;
+ local r_item = roster[jid];
if r_item then
+ local to_bare = node and (node.."@"..host) or host; -- bare JID
+ if r_item.subscription == "both" or r_item.subscription == "from" or (roster.pending and roster.pending[jid]) then
+ core_post_stanza(session, st.presence({type="unsubscribed", from=session.full_jid, to=to_bare}));
+ end
+ if r_item.subscription == "both" or r_item.subscription == "to" or r_item.ask then
+ core_post_stanza(session, st.presence({type="unsubscribe", from=session.full_jid, to=to_bare}));
+ end
local success, err_type, err_cond, err_msg = rm_remove_from_roster(session, jid);
if success then
session.send(st.reply(stanza));
rm_roster_push(from_node, from_host, jid);
- local to_bare = node and (node.."@"..host) or host; -- bare JID
- if r_item.subscription == "both" or r_item.subscription == "from" then
- core_post_stanza(session, st.presence({type="unsubscribed", from=session.full_jid, to=to_bare}));
- elseif r_item.subscription == "both" or r_item.subscription == "to" then
- core_post_stanza(session, st.presence({type="unsubscribe", from=session.full_jid, to=to_bare}));
- end
else
session.send(st.error_reply(stanza, err_type, err_cond, err_msg));
end
diff --git a/plugins/mod_saslauth.lua b/plugins/mod_saslauth.lua
index c0f57cd7..001f14e2 100644
--- a/plugins/mod_saslauth.lua
+++ b/plugins/mod_saslauth.lua
@@ -1,7 +1,7 @@
-- Prosody IM
-- 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.
--
@@ -35,6 +35,27 @@ local xmlns_stanzas ='urn:ietf:params:xml:ns:xmpp-stanzas';
local new_sasl = require "util.sasl".new;
+default_authentication_profile = {
+ plain = function(username, realm)
+ local prepped_username = nodeprep(username);
+ if not prepped_username then
+ log("debug", "NODEprep failed on username: %s", username);
+ return "", nil;
+ end
+ local password = usermanager_get_password(prepped_username, realm);
+ if not password then
+ return "", nil;
+ end
+ return password, true;
+ end
+};
+
+anonymous_authentication_profile = {
+ anonymous = function(username, realm)
+ return true; -- for normal usage you should always return true here
+ end
+}
+
local function build_reply(status, ret, err_msg)
local reply = st.stanza(status, {xmlns = xmlns_sasl});
if status == "challenge" then
@@ -54,50 +75,18 @@ end
local function handle_status(session, status)
if status == "failure" then
- session.sasl_handler = nil;
+ session.sasl_handler = session.sasl_handler:clean_clone();
elseif status == "success" then
local username = nodeprep(session.sasl_handler.username);
- session.sasl_handler = nil;
if not username then -- TODO move this to sessionmanager
module:log("warn", "SASL succeeded but we didn't get a username!");
session.sasl_handler = nil;
session:reset_stream();
return;
- end
- sm_make_authenticated(session, username);
- session:reset_stream();
- end
-end
-
-local function credentials_callback(mechanism, ...)
- if mechanism == "PLAIN" then
- local username, hostname, password = ...;
- username = nodeprep(username);
- if not username then
- return false;
- end
- local response = usermanager_validate_credentials(hostname, username, password, mechanism);
- if response == nil then
- return false;
- else
- return response;
- end
- elseif mechanism == "DIGEST-MD5" then
- local function func(x) return x; end
- local node, domain, realm, decoder = ...;
- local prepped_node = nodeprep(node);
- if not prepped_node then
- return func, nil;
- end
- local password = usermanager_get_password(prepped_node, domain);
- if password then
- if decoder then
- node, realm, password = decoder(node), decoder(realm), decoder(password);
- end
- return func, md5(node..":"..realm..":"..password);
- else
- return func, nil;
end
+ sm_make_authenticated(session, session.sasl_handler.username);
+ session.sasl_handler = nil;
+ session:reset_stream();
end
end
@@ -111,8 +100,8 @@ local function sasl_handler(session, stanza)
elseif stanza.attr.mechanism == "ANONYMOUS" then
return session.send(build_reply("failure", "mechanism-too-weak"));
end
- session.sasl_handler = new_sasl(stanza.attr.mechanism, session.host, credentials_callback);
- if not session.sasl_handler then
+ local valid_mechanism = session.sasl_handler:select(stanza.attr.mechanism);
+ if not valid_mechanism then
return session.send(build_reply("failure", "invalid-mechanism"));
end
elseif not session.sasl_handler then
@@ -128,7 +117,7 @@ local function sasl_handler(session, stanza)
return;
end
end
- local status, ret, err_msg = session.sasl_handler:feed(text);
+ local status, ret, err_msg = session.sasl_handler:process(text);
handle_status(session, status);
local s = build_reply(status, ret, err_msg);
log("debug", "sasl reply: %s", tostring(s));
@@ -148,16 +137,18 @@ module:add_event_hook("stream-features",
if secure_auth_only and not session.secure then
return;
end
- features:tag("mechanisms", mechanisms_attr);
- -- TODO: Provide PLAIN only if TLS is active, this is a SHOULD from the introduction of RFC 4616. This behavior could be overridden via configuration but will issuing a warning or so.
- if config.get(session.host or "*", "core", "anonymous_login") then
- features:tag("mechanism"):text("ANONYMOUS"):up();
- else
- local mechanisms = usermanager_get_supported_methods(session.host or "*");
- for k, v in pairs(mechanisms) do
- features:tag("mechanism"):text(k):up();
- end
+ if module:get_option("anonymous_login") then
+ session.sasl_handler = new_sasl(session.host, anonymous_authentication_profile);
+ else
+ session.sasl_handler = new_sasl(session.host, default_authentication_profile);
+ if not (module:get_option("allow_unencrypted_plain_auth")) and not session.secure then
+ session.sasl_handler:forbidden({"PLAIN"});
end
+ end
+ features:tag("mechanisms", mechanisms_attr);
+ for k, v in pairs(session.sasl_handler:mechanisms()) do
+ features:tag("mechanism"):text(v):up();
+ end
features:up();
else
features:tag("bind", bind_attr):tag("required"):up():up();
diff --git a/plugins/mod_tls.lua b/plugins/mod_tls.lua
index 8a450803..706b42c9 100644
--- a/plugins/mod_tls.lua
+++ b/plugins/mod_tls.lua
@@ -20,9 +20,9 @@ module:add_handler("c2s_unauthed", "starttls", xmlns_starttls,
session.send(st.stanza("proceed", { xmlns = xmlns_starttls }));
session:reset_stream();
if session.host and hosts[session.host].ssl_ctx_in then
- session.conn.set_sslctx(hosts[session.host].ssl_ctx_in);
+ session.conn:set_sslctx(hosts[session.host].ssl_ctx_in);
end
- session.conn.starttls();
+ session.conn:starttls();
session.log("info", "TLS negotiation started...");
session.secure = false;
else
@@ -37,9 +37,9 @@ module:add_handler("s2sin_unauthed", "starttls", xmlns_starttls,
session.sends2s(st.stanza("proceed", { xmlns = xmlns_starttls }));
session:reset_stream();
if session.to_host and hosts[session.to_host].ssl_ctx_in then
- session.conn.set_sslctx(hosts[session.to_host].ssl_ctx_in);
+ session.conn:set_sslctx(hosts[session.to_host].ssl_ctx_in);
end
- session.conn.starttls();
+ session.conn:starttls();
session.log("info", "TLS negotiation started for incoming s2s...");
session.secure = false;
else
@@ -91,7 +91,7 @@ module:hook_stanza(xmlns_starttls, "proceed",
module:log("debug", "Proceeding with TLS on s2sout...");
local format, to_host, from_host = string.format, session.to_host, session.from_host;
session:reset_stream();
- session.conn.starttls(true);
+ session.conn:starttls(true);
session.secure = false;
return true;
end);
diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua
index 3a185e17..002498af 100644
--- a/plugins/muc/muc.lib.lua
+++ b/plugins/muc/muc.lib.lua
@@ -128,19 +128,21 @@ function room_mt:broadcast_presence(stanza, sid, code, nick)
end
end
function room_mt:broadcast_message(stanza, historic)
+ local to = stanza.attr.to;
for occupant, o_data in pairs(self._occupants) do
for jid in pairs(o_data.sessions) do
stanza.attr.to = jid;
self:_route_stanza(stanza);
end
end
+ stanza.attr.to = to;
if historic then -- add to history
local history = self._data['history'];
if not history then history = {}; self._data['history'] = history; end
- -- stanza = st.clone(stanza);
+ stanza = st.clone(stanza);
stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = muc_domain, stamp = datetime.datetime()}):up(); -- XEP-0203
stanza:tag("x", {xmlns = "jabber:x:delay", from = muc_domain, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated)
- t_insert(history, st.clone(st.preserialize(stanza)));
+ t_insert(history, st.preserialize(stanza));
while #history > history_length do t_remove(history, 1) end
end
end
@@ -387,51 +389,70 @@ function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc
end
end
-function room_mt:handle_form(origin, stanza)
- if self:get_affiliation(stanza.attr.from) ~= "owner" then origin.send(st.error_reply(stanza, "auth", "forbidden")); return; end
- if stanza.attr.type == "get" then
- local title = "Configuration for "..self.jid;
- origin.send(st.reply(stanza):query("http://jabber.org/protocol/muc#owner")
- :tag("x", {xmlns='jabber:x:data', type='form'})
- :tag("title"):text(title):up()
- :tag("instructions"):text(title):up()
- :tag("field", {type='hidden', var='FORM_TYPE'}):tag("value"):text("http://jabber.org/protocol/muc#roomconfig"):up():up()
- :tag("field", {type='boolean', label='Make Room Persistent?', var='muc#roomconfig_persistentroom'})
- :tag("value"):text(self._data.persistent and "1" or "0"):up()
- :up()
- :tag("field", {type='boolean', label='Make Room Publicly Searchable?', var='muc#roomconfig_publicroom'})
- :tag("value"):text(self._data.hidden and "0" or "1"):up()
- :up()
- );
- elseif stanza.attr.type == "set" then
- local query = stanza.tags[1];
- local form;
- for _, tag in ipairs(query.tags) do if tag.name == "x" and tag.attr.xmlns == "jabber:x:data" then form = tag; break; end end
- if not form then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); return; end
- if form.attr.type == "cancel" then origin.send(st.reply(stanza)); return; end
- if form.attr.type ~= "submit" then origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
- local fields = {};
- for _, field in pairs(form.tags) do
- if field.name == "field" and field.attr.var and field.tags[1].name == "value" and #field.tags[1].tags == 0 then
- fields[field.attr.var] = field.tags[1][1] or "";
- end
+function room_mt:send_form(origin, stanza)
+ local title = "Configuration for "..self.jid;
+ origin.send(st.reply(stanza):query("http://jabber.org/protocol/muc#owner")
+ :tag("x", {xmlns='jabber:x:data', type='form'})
+ :tag("title"):text(title):up()
+ :tag("instructions"):text(title):up()
+ :tag("field", {type='hidden', var='FORM_TYPE'}):tag("value"):text("http://jabber.org/protocol/muc#roomconfig"):up():up()
+ :tag("field", {type='boolean', label='Make Room Persistent?', var='muc#roomconfig_persistentroom'})
+ :tag("value"):text(self._data.persistent and "1" or "0"):up()
+ :up()
+ :tag("field", {type='boolean', label='Make Room Publicly Searchable?', var='muc#roomconfig_publicroom'})
+ :tag("value"):text(self._data.hidden and "0" or "1"):up()
+ :up()
+ );
+end
+
+function room_mt:process_form(origin, stanza)
+ local query = stanza.tags[1];
+ local form;
+ for _, tag in ipairs(query.tags) do if tag.name == "x" and tag.attr.xmlns == "jabber:x:data" then form = tag; break; end end
+ if not form then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); return; end
+ if form.attr.type == "cancel" then origin.send(st.reply(stanza)); return; end
+ if form.attr.type ~= "submit" then origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
+ local fields = {};
+ for _, field in pairs(form.tags) do
+ if field.name == "field" and field.attr.var and field.tags[1].name == "value" and #field.tags[1].tags == 0 then
+ fields[field.attr.var] = field.tags[1][1] or "";
end
- if fields.FORM_TYPE ~= "http://jabber.org/protocol/muc#roomconfig" then origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
+ end
+ if fields.FORM_TYPE ~= "http://jabber.org/protocol/muc#roomconfig" then origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
- local persistent = fields['muc#roomconfig_persistentroom'];
- if persistent == "0" or persistent == "false" then persistent = nil; elseif persistent == "1" or persistent == "true" then persistent = true;
- else origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
- self._data.persistent = persistent;
- module:log("debug", "persistent=%s", tostring(persistent));
+ local persistent = fields['muc#roomconfig_persistentroom'];
+ if persistent == "0" or persistent == "false" then persistent = nil; elseif persistent == "1" or persistent == "true" then persistent = true;
+ else origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
+ self._data.persistent = persistent;
+ module:log("debug", "persistent=%s", tostring(persistent));
- local public = fields['muc#roomconfig_publicroom'];
- if public == "0" or public == "false" then public = nil; elseif public == "1" or public == "true" then public = true;
- else origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
- self._data.hidden = not public and true or nil;
+ local public = fields['muc#roomconfig_publicroom'];
+ if public == "0" or public == "false" then public = nil; elseif public == "1" or public == "true" then public = true;
+ else origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
+ self._data.hidden = not public and true or nil;
- if self.save then self:save(true); end
- origin.send(st.reply(stanza));
+ if self.save then self:save(true); end
+ origin.send(st.reply(stanza));
+end
+
+function room_mt:destroy(newjid, reason, password)
+ local pr = st.presence({type = "unavailable"})
+ :tag("x", {xmlns = "http://jabber.org/protocol/muc#user"})
+ :tag("item", { affiliation='none', role='none' }):up()
+ :tag("destroy", {jid=newjid})
+ if reason then pr:tag("reason"):text(reason):up(); end
+ if password then pr:tag("password"):text(password):up(); end
+ for nick, occupant in pairs(self._occupants) do
+ pr.attr.from = nick;
+ for jid in pairs(occupant.sessions) do
+ pr.attr.to = jid;
+ self:_route_stanza(pr);
+ self._jid_nick[jid] = nil;
+ end
+ self._occupants[nick] = nil;
end
+ self._data.persistent = nil;
+ if self.save then self:save(true); end
end
function room_mt:handle_to_room(origin, stanza) -- presence changes and groupchat messages, along with disco/etc
@@ -509,7 +530,30 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha
origin.send(st.error_reply(stanza, "cancel", "bad-request"));
end
elseif xmlns == "http://jabber.org/protocol/muc#owner" and (type == "get" or type == "set") and stanza.tags[1].name == "query" then
- self:handle_form(origin, stanza);
+ if self:get_affiliation(stanza.attr.from) ~= "owner" then
+ origin.send(st.error_reply(stanza, "auth", "forbidden"));
+ elseif stanza.attr.type == "get" then
+ self:send_form(origin, stanza);
+ elseif stanza.attr.type == "set" then
+ local child = stanza.tags[1].tags[1];
+ if not child then
+ origin.send(st.error_reply(stanza, "auth", "bad-request"));
+ elseif child.name == "destroy" then
+ local newjid = child.attr.jid;
+ local reason, password;
+ for _,tag in ipairs(child.tags) do
+ if tag.name == "reason" then
+ reason = #tag.tags == 0 and tag[1];
+ elseif tag.name == "password" then
+ password = #tag.tags == 0 and tag[1];
+ end
+ end
+ self:destroy(newjid, reason, password);
+ origin.send(st.reply(stanza));
+ else
+ self:process_form(origin, stanza);
+ end
+ end
elseif type == "set" or type == "get" then
origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
end
@@ -517,17 +561,26 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha
local from, to = stanza.attr.from, stanza.attr.to;
local room = jid_bare(to);
local current_nick = self._jid_nick[from];
- if not current_nick then -- not in room
+ local occupant = self._occupants[current_nick];
+ if not occupant then -- not in room
origin.send(st.error_reply(stanza, "cancel", "not-acceptable"));
+ elseif occupant.role == "visitor" then
+ origin.send(st.error_reply(stanza, "cancel", "forbidden"));
else
local from = stanza.attr.from;
stanza.attr.from = current_nick;
local subject = getText(stanza, {"subject"});
if subject then
- self:set_subject(current_nick, subject); -- TODO use broadcast_message_stanza
+ if occupant.role == "moderator" then
+ self:set_subject(current_nick, subject); -- TODO use broadcast_message_stanza
+ else
+ stanza.attr.from = from;
+ origin.send(st.error_reply(stanza, "cancel", "forbidden"));
+ end
else
self:broadcast_message(stanza, true);
end
+ stanza.attr.from = from;
end
elseif stanza.name == "message" and type == "error" and is_kickable_error(stanza) then
local current_nick = self._jid_nick[stanza.attr.from];