aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/componentmanager.lua11
-rw-r--r--core/eventmanager2.lua6
-rw-r--r--core/hostmanager.lua3
-rw-r--r--core/modulemanager.lua14
-rw-r--r--core/stanza_router.lua109
-rw-r--r--core/xmlhandlers.lua47
-rw-r--r--fallbacks/bit.lua137
-rw-r--r--net/http.lua4
-rw-r--r--net/server.lua4
-rw-r--r--net/xmppclient_listener.lua6
-rw-r--r--net/xmppserver_listener.lua7
-rw-r--r--plugins/mod_bosh.lua1
-rw-r--r--plugins/mod_muc.lua65
-rw-r--r--plugins/mod_pep.lua48
-rw-r--r--plugins/mod_presence.lua280
-rw-r--r--plugins/mod_register.lua6
-rw-r--r--plugins/mod_roster.lua8
-rw-r--r--plugins/mod_welcome.lua23
-rw-r--r--prosody.cfg.lua.dist3
-rw-r--r--util/events.lua39
-rw-r--r--util/jid.lua12
-rw-r--r--util/muc.lua422
-rw-r--r--util/sasl.lua73
-rw-r--r--util/serialization.lua2
-rw-r--r--util/stanza.lua3
25 files changed, 1130 insertions, 203 deletions
diff --git a/core/componentmanager.lua b/core/componentmanager.lua
index 93e2ae7b..ff60de28 100644
--- a/core/componentmanager.lua
+++ b/core/componentmanager.lua
@@ -15,6 +15,7 @@ local eventmanager = require "core.eventmanager";
local modulemanager = require "core.modulemanager";
local core_route_stanza = core_route_stanza;
local jid_split = require "util.jid".split;
+local events_new = require "util.events".new;
local st = require "util.stanza";
local hosts = hosts;
@@ -67,9 +68,10 @@ eventmanager.add_event_hook("server-starting", load_enabled_components);
function handle_stanza(origin, stanza)
local node, host = jid_split(stanza.attr.to);
local component = nil;
- if not component then component = components[stanza.attr.to]; end -- hack to allow hooking node@server/resource and server/resource
- if not component then component = components[node.."@"..host]; end -- hack to allow hooking node@server
- if not component then component = components[host]; end
+ if host then
+ if node then component = components[node.."@"..host]; end -- hack to allow hooking node@server
+ if not component then component = components[host]; end
+ end
if component then
log("debug", "%s stanza being handled by component: %s", stanza.name, host);
component(origin, stanza, hosts[host]);
@@ -80,8 +82,7 @@ end
function create_component(host, component)
-- TODO check for host well-formedness
- local session = session or { type = "component", host = host, connected = true, s2sout = {} };
- return session;
+ return { type = "component", host = host, connected = true, s2sout = {}, events = events_new() };
end
function register_component(host, component, session)
diff --git a/core/eventmanager2.lua b/core/eventmanager2.lua
new file mode 100644
index 00000000..4783d0a9
--- /dev/null
+++ b/core/eventmanager2.lua
@@ -0,0 +1,6 @@
+
+local events = require "util.events".new();
+
+module "eventmanager"
+
+return events;
diff --git a/core/hostmanager.lua b/core/hostmanager.lua
index 97c742da..a72e5f61 100644
--- a/core/hostmanager.lua
+++ b/core/hostmanager.lua
@@ -2,6 +2,7 @@
local hosts = hosts;
local configmanager = require "core.configmanager";
local eventmanager = require "core.eventmanager";
+local events_new = require "util.events".new;
local log = require "util.logger".init("hostmanager");
@@ -26,7 +27,7 @@ end
eventmanager.add_event_hook("server-starting", load_enabled_hosts);
function activate(host, host_config)
- hosts[host] = {type = "local", connected = true, sessions = {}, host = host, s2sout = {} };
+ hosts[host] = {type = "local", connected = true, sessions = {}, host = host, s2sout = {}, events = events_new() };
log((hosts_loaded_once and "info") or "debug", "Activated host: %s", host);
eventmanager.fire_event("host-activated", host, host_config);
end
diff --git a/core/modulemanager.lua b/core/modulemanager.lua
index 6dbc8c53..57a44c29 100644
--- a/core/modulemanager.lua
+++ b/core/modulemanager.lua
@@ -17,6 +17,7 @@ local eventmanager = require "core.eventmanager";
local config = require "core.configmanager";
local multitable_new = require "util.multitable".new;
local register_actions = require "core.actions".register;
+local st = require "util.stanza";
local hosts = hosts;
@@ -226,7 +227,14 @@ function handle_stanza(host, origin, stanza)
(handlers[1])(origin, stanza);
return true;
else
- log("debug", "Stanza unhandled by any modules, xmlns: %s", stanza.attr.xmlns); -- we didn't handle it
+ log("debug", "Unhandled %s stanza: %s; xmlns=%s", origin.type, stanza.name, xmlns); -- we didn't handle it
+ if stanza.attr.xmlns == "jabber:client" then
+ if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then
+ origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
+ end
+ elseif not(name == "features" and xmlns == "http://etherx.jabber.org/streams") then -- FIXME remove check once we handle S2S features
+ origin:close("unsupported-stanza-type");
+ end
end
end
@@ -342,6 +350,10 @@ function api:add_event_hook(name, handler)
event_hooks:set(self.host, self.name, name, handler, true);
end
+function api:fire_event(...)
+ return eventmanager.fire_event(...);
+end
+
--------------------------------------------------------------------
local actions = {};
diff --git a/core/stanza_router.lua b/core/stanza_router.lua
index ae6b944f..6fa41232 100644
--- a/core/stanza_router.lua
+++ b/core/stanza_router.lua
@@ -10,6 +10,8 @@
local log = require "util.logger".init("stanzarouter")
+local hosts = _G.hosts;
+
local st = require "util.stanza";
local send_s2s = require "core.s2smanager".send_to_host;
local user_exists = require "core.usermanager".user_exists;
@@ -24,9 +26,9 @@ local s2s_make_authenticated = require "core.s2smanager".make_authenticated;
local modules_handle_stanza = require "core.modulemanager".handle_stanza;
local component_handle_stanza = require "core.componentmanager".handle_stanza;
-local handle_outbound_presence_subscriptions_and_probes = require "core.presencemanager".handle_outbound_presence_subscriptions_and_probes;
-local handle_inbound_presence_subscriptions_and_probes = require "core.presencemanager".handle_inbound_presence_subscriptions_and_probes;
-local handle_normal_presence = require "core.presencemanager".handle_normal_presence;
+local handle_outbound_presence_subscriptions_and_probes = function()end;--require "core.presencemanager".handle_outbound_presence_subscriptions_and_probes;
+local handle_inbound_presence_subscriptions_and_probes = function()end;--require "core.presencemanager".handle_inbound_presence_subscriptions_and_probes;
+local handle_normal_presence = function()end;--require "core.presencemanager".handle_normal_presence;
local format = string.format;
local tostring = tostring;
@@ -40,18 +42,16 @@ local ipairs = ipairs;
local jid_split = require "util.jid".split;
local jid_prepped_split = require "util.jid".prepped_split;
local print = print;
-local function checked_error_reply(origin, stanza)
- if (stanza.attr.xmlns == "jabber:client" or stanza.attr.xmlns == "jabber:server") and stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then
- origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); -- FIXME correct error?
- end
-end
+local fire_event = require "core.eventmanager2".fire_event;
function core_process_stanza(origin, stanza)
(origin.log or log)("debug", "Received[%s]: %s", origin.type, stanza:top_tag())
- if not stanza.attr.xmlns then stanza.attr.xmlns = "jabber:client"; end -- FIXME Hack. This should be removed when we fix namespace handling.
+ -- Currently we guarantee every stanza to have an xmlns, should we keep this rule?
+ if not stanza.attr.xmlns then stanza.attr.xmlns = "jabber:client"; end
+
-- TODO verify validity of stanza (as well as JID validity)
- if stanza.attr.xmlns == "error" and #stanza.tags == 0 then return; end -- TODO invalid stanza, log
+ if stanza.attr.type == "error" and #stanza.tags == 0 then return; end -- TODO invalid stanza, log
if stanza.name == "iq" then
if (stanza.attr.type == "set" or stanza.attr.type == "get") and #stanza.tags ~= 1 then
origin.send(st.error_reply(stanza, "modify", "bad-request"));
@@ -103,69 +103,42 @@ function core_process_stanza(origin, stanza)
return; -- FIXME what should we do here?
end]] -- FIXME
- -- FIXME do stanzas not of jabber:client get handled by components?
- if (origin.type == "s2sin" or origin.type == "c2s" or origin.type == "component") and (not xmlns or xmlns == "jabber:server" or xmlns == "jabber:client") then
+ if (origin.type == "s2sin" or origin.type == "c2s" or origin.type == "component") and xmlns == "jabber:client" then
if origin.type == "s2sin" and not origin.dummy then
local host_status = origin.hosts[from_host];
if not host_status or not host_status.authed then -- remote server trying to impersonate some other server?
- log("warn", "Received a stanza claiming to be from %s, over a conn authed for %s!", from_host, origin.from_host);
+ log("warn", "Received a stanza claiming to be from %s, over a stream authed for %s!", from_host, origin.from_host);
return; -- FIXME what should we do here? does this work with subdomains?
end
end
- if origin.type == "c2s" and stanza.name == "presence" and to ~= nil and not(origin.roster[to_bare] and (origin.roster[to_bare].subscription == "both" or origin.roster[to_bare].subscription == "from")) then -- directed presence
- origin.directed = origin.directed or {};
- origin.directed[to] = true;
- --t_insert(origin.directed, to); -- FIXME does it make more sense to add to_bare rather than to?
- end
- if not to then
- core_handle_stanza(origin, stanza);
- elseif hosts[to] and hosts[to].type == "local" then -- directed at a local server
- core_handle_stanza(origin, stanza);
- elseif stanza.attr.xmlns and stanza.attr.xmlns ~= "jabber:client" and stanza.attr.xmlns ~= "jabber:server" then
- modules_handle_stanza(host or origin.host or origin.to_host, origin, stanza);
- elseif origin.type == "c2s" and stanza.name == "presence" and stanza.attr.type ~= nil and stanza.attr.type ~= "unavailable" and stanza.attr.type ~= "error" then
- handle_outbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare, core_route_stanza);
- elseif hosts[to] and hosts[to].type == "component" then -- hack to allow components to handle node@server/resource and server/resource
- component_handle_stanza(origin, stanza);
- elseif hosts[to_bare] and hosts[to_bare].type == "component" then -- hack to allow components to handle node@server
- component_handle_stanza(origin, stanza);
- elseif hosts[host] and hosts[host].type == "component" then -- directed at a component
- component_handle_stanza(origin, stanza);
- elseif hosts[host] and hosts[host].type == "local" and stanza.name == "iq" and not resource then -- directed at bare JID
- core_handle_stanza(origin, stanza);
- else
- core_route_stanza(origin, stanza);
- end
+ core_post_stanza(origin, stanza);
else
- core_handle_stanza(origin, stanza);
+ modules_handle_stanza(host or origin.host or origin.to_host, origin, stanza);
end
end
--- This function handles stanzas which are not routed any further,
--- that is, they are handled by this server
-function core_handle_stanza(origin, stanza)
- -- Handlers
- if modules_handle_stanza(select(2, jid_split(stanza.attr.to)) or origin.host, origin, stanza) then return; end
- if origin.type == "c2s" or origin.type == "s2sin" then
- if origin.type == "c2s" then
- if stanza.name == "presence" and origin.roster then
- if stanza.attr.type == nil or stanza.attr.type == "unavailable" and stanza.attr.type ~= "error" then
- handle_normal_presence(origin, stanza, core_route_stanza);
- else
- log("warn", "Unhandled c2s presence: %s", tostring(stanza));
- checked_error_reply(origin, stanza);
- end
- else
- log("warn", "Unhandled c2s stanza: %s", tostring(stanza));
- checked_error_reply(origin, stanza);
- end
- else -- s2s stanzas
- log("warn", "Unhandled s2s stanza: %s", tostring(stanza));
- checked_error_reply(origin, stanza);
- end
+function core_post_stanza(origin, stanza)
+ local to = stanza.attr.to;
+ local node, host, resource = jid_split(to);
+ local to_bare = node and (node.."@"..host) or host; -- bare JID
+
+ local event_data = {origin=origin, stanza=stanza};
+ if host and fire_event(host.."/"..stanza.name, event_data) then
+ -- event handled
+ elseif stanza.name == "presence" and origin.host and fire_event(origin.host.."/"..stanza.name, event_data) then
+ -- event handled
+ elseif not to then
+ modules_handle_stanza(host or origin.host or origin.to_host, origin, stanza);
+ elseif hosts[to] and hosts[to].type == "local" then -- directed at a local server
+ modules_handle_stanza(host or origin.host or origin.to_host, origin, stanza);
+ elseif hosts[to_bare] and hosts[to_bare].type == "component" then -- hack to allow components to handle node@server
+ component_handle_stanza(origin, stanza);
+ elseif hosts[host] and hosts[host].type == "component" then -- directed at a component
+ component_handle_stanza(origin, stanza);
+ elseif hosts[host] and hosts[host].type == "local" and stanza.name == "iq" and not resource then -- directed at bare JID
+ modules_handle_stanza(host or origin.host or origin.to_host, origin, stanza);
else
- log("warn", "Unhandled %s stanza: %s", origin.type, tostring(stanza));
- checked_error_reply(origin, stanza);
+ core_route_stanza(origin, stanza);
end
end
@@ -185,9 +158,7 @@ function core_route_stanza(origin, stanza)
origin = origin or hosts[from_host];
if not origin then return false; end
- if hosts[to] and hosts[to].type == "component" then -- hack to allow components to handle node@server/resource and server/resource
- return component_handle_stanza(origin, stanza);
- elseif hosts[to_bare] and hosts[to_bare].type == "component" then -- hack to allow components to handle node@server
+ if hosts[to_bare] and hosts[to_bare].type == "component" then -- hack to allow components to handle node@server
return component_handle_stanza(origin, stanza);
elseif hosts[host] and hosts[host].type == "component" then -- directed at a component
return component_handle_stanza(origin, stanza);
@@ -201,7 +172,9 @@ function core_route_stanza(origin, stanza)
local user = host_session.sessions[node];
if user then
local res = user.sessions[resource];
- if not res then
+ if res then -- resource is online...
+ res.send(stanza); -- Yay \o/
+ else
-- if we get here, resource was not specified or was unavailable
if stanza.name == "presence" then
if stanza.attr.type ~= nil and stanza.attr.type ~= "unavailable" and stanza.attr.type ~= "error" then
@@ -252,10 +225,6 @@ function core_route_stanza(origin, stanza)
elseif stanza.attr.type == "get" or stanza.attr.type == "set" then
origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
end
- else
- -- User + resource is online...
- stanza.attr.to = res.full_jid; -- reset at the end of function
- res.send(stanza); -- Yay \o/
end
else
-- user not online
diff --git a/core/xmlhandlers.lua b/core/xmlhandlers.lua
index 7e09921c..573a9604 100644
--- a/core/xmlhandlers.lua
+++ b/core/xmlhandlers.lua
@@ -14,15 +14,8 @@ local st = stanza;
local tostring = tostring;
local pairs = pairs;
local ipairs = ipairs;
-local type = type;
-local print = print;
-local format = string.format;
-local m_random = math.random;
local t_insert = table.insert;
-local t_remove = table.remove;
local t_concat = table.concat;
-local t_concatall = function (t, sep) local tt = {}; for _, s in ipairs(t) do t_insert(tt, tostring(s)); end return t_concat(tt, sep); end
-local sm_destroy_session = import("core.sessionmanager", "destroy_session");
local default_log = require "util.logger".init("xmlhandlers");
@@ -68,15 +61,13 @@ function init_xmlhandlers(session, stream_callbacks)
-- FIXME !!!!!
for i, k in ipairs(attr) do
- if type(k) == "string" then
- local ns, nm = k:match("^([^|]+)|?([^|]-)$")
- if ns and nm then
- ns = ns_prefixes[ns];
- if ns then
- attr[ns..":"..nm] = attr[k];
- attr[i] = ns..":"..nm;
- attr[k] = nil;
- end
+ local ns, nm = k:match("^([^|]+)|?([^|]-)$")
+ if ns and nm then
+ ns = ns_prefixes[ns];
+ if ns then
+ attr[ns..":"..nm] = attr[k];
+ attr[i] = ns..":"..nm;
+ attr[k] = nil;
end
end
end
@@ -129,19 +120,17 @@ function init_xmlhandlers(session, stream_callbacks)
cb_error(session, "parse-error", "unexpected-element-close", name);
end
end
- if stanza then
- if #chardata > 0 then
- -- We have some character data in the buffer
- stanza:text(t_concat(chardata));
- chardata = {};
- end
- -- Complete stanza
- if #stanza.last_add == 0 then
- cb_handlestanza(session, stanza);
- stanza = nil;
- else
- stanza:up();
- end
+ if #chardata > 0 then
+ -- We have some character data in the buffer
+ stanza:text(t_concat(chardata));
+ chardata = {};
+ end
+ -- Complete stanza
+ if #stanza.last_add == 0 then
+ cb_handlestanza(session, stanza);
+ stanza = nil;
+ else
+ stanza:up();
end
end
return xml_handlers;
diff --git a/fallbacks/bit.lua b/fallbacks/bit.lua
new file mode 100644
index 00000000..86e3b4a4
--- /dev/null
+++ b/fallbacks/bit.lua
@@ -0,0 +1,137 @@
+
+local type = type;
+local tonumber = tonumber;
+local setmetatable = setmetatable;
+local error = error;
+local tostring = tostring;
+local print = print;
+
+local xor_map = {[0]=0;[1]=1;[2]=2;[3]=3;[4]=4;[5]=5;[6]=6;[7]=7;[8]=8;[9]=9;[10]=10;[11]=11;[12]=12;[13]=13;[14]=14;[15]=15;[16]=1;[17]=0;[18]=3;[19]=2;[20]=5;[21]=4;[22]=7;[23]=6;[24]=9;[25]=8;[26]=11;[27]=10;[28]=13;[29]=12;[30]=15;[31]=14;[32]=2;[33]=3;[34]=0;[35]=1;[36]=6;[37]=7;[38]=4;[39]=5;[40]=10;[41]=11;[42]=8;[43]=9;[44]=14;[45]=15;[46]=12;[47]=13;[48]=3;[49]=2;[50]=1;[51]=0;[52]=7;[53]=6;[54]=5;[55]=4;[56]=11;[57]=10;[58]=9;[59]=8;[60]=15;[61]=14;[62]=13;[63]=12;[64]=4;[65]=5;[66]=6;[67]=7;[68]=0;[69]=1;[70]=2;[71]=3;[72]=12;[73]=13;[74]=14;[75]=15;[76]=8;[77]=9;[78]=10;[79]=11;[80]=5;[81]=4;[82]=7;[83]=6;[84]=1;[85]=0;[86]=3;[87]=2;[88]=13;[89]=12;[90]=15;[91]=14;[92]=9;[93]=8;[94]=11;[95]=10;[96]=6;[97]=7;[98]=4;[99]=5;[100]=2;[101]=3;[102]=0;[103]=1;[104]=14;[105]=15;[106]=12;[107]=13;[108]=10;[109]=11;[110]=8;[111]=9;[112]=7;[113]=6;[114]=5;[115]=4;[116]=3;[117]=2;[118]=1;[119]=0;[120]=15;[121]=14;[122]=13;[123]=12;[124]=11;[125]=10;[126]=9;[127]=8;[128]=8;[129]=9;[130]=10;[131]=11;[132]=12;[133]=13;[134]=14;[135]=15;[136]=0;[137]=1;[138]=2;[139]=3;[140]=4;[141]=5;[142]=6;[143]=7;[144]=9;[145]=8;[146]=11;[147]=10;[148]=13;[149]=12;[150]=15;[151]=14;[152]=1;[153]=0;[154]=3;[155]=2;[156]=5;[157]=4;[158]=7;[159]=6;[160]=10;[161]=11;[162]=8;[163]=9;[164]=14;[165]=15;[166]=12;[167]=13;[168]=2;[169]=3;[170]=0;[171]=1;[172]=6;[173]=7;[174]=4;[175]=5;[176]=11;[177]=10;[178]=9;[179]=8;[180]=15;[181]=14;[182]=13;[183]=12;[184]=3;[185]=2;[186]=1;[187]=0;[188]=7;[189]=6;[190]=5;[191]=4;[192]=12;[193]=13;[194]=14;[195]=15;[196]=8;[197]=9;[198]=10;[199]=11;[200]=4;[201]=5;[202]=6;[203]=7;[204]=0;[205]=1;[206]=2;[207]=3;[208]=13;[209]=12;[210]=15;[211]=14;[212]=9;[213]=8;[214]=11;[215]=10;[216]=5;[217]=4;[218]=7;[219]=6;[220]=1;[221]=0;[222]=3;[223]=2;[224]=14;[225]=15;[226]=12;[227]=13;[228]=10;[229]=11;[230]=8;[231]=9;[232]=6;[233]=7;[234]=4;[235]=5;[236]=2;[237]=3;[238]=0;[239]=1;[240]=15;[241]=14;[242]=13;[243]=12;[244]=11;[245]=10;[246]=9;[247]=8;[248]=7;[249]=6;[250]=5;[251]=4;[252]=3;[253]=2;[254]=1;[255]=0;};
+local or_map = {[0]=0;[1]=1;[2]=2;[3]=3;[4]=4;[5]=5;[6]=6;[7]=7;[8]=8;[9]=9;[10]=10;[11]=11;[12]=12;[13]=13;[14]=14;[15]=15;[16]=1;[17]=1;[18]=3;[19]=3;[20]=5;[21]=5;[22]=7;[23]=7;[24]=9;[25]=9;[26]=11;[27]=11;[28]=13;[29]=13;[30]=15;[31]=15;[32]=2;[33]=3;[34]=2;[35]=3;[36]=6;[37]=7;[38]=6;[39]=7;[40]=10;[41]=11;[42]=10;[43]=11;[44]=14;[45]=15;[46]=14;[47]=15;[48]=3;[49]=3;[50]=3;[51]=3;[52]=7;[53]=7;[54]=7;[55]=7;[56]=11;[57]=11;[58]=11;[59]=11;[60]=15;[61]=15;[62]=15;[63]=15;[64]=4;[65]=5;[66]=6;[67]=7;[68]=4;[69]=5;[70]=6;[71]=7;[72]=12;[73]=13;[74]=14;[75]=15;[76]=12;[77]=13;[78]=14;[79]=15;[80]=5;[81]=5;[82]=7;[83]=7;[84]=5;[85]=5;[86]=7;[87]=7;[88]=13;[89]=13;[90]=15;[91]=15;[92]=13;[93]=13;[94]=15;[95]=15;[96]=6;[97]=7;[98]=6;[99]=7;[100]=6;[101]=7;[102]=6;[103]=7;[104]=14;[105]=15;[106]=14;[107]=15;[108]=14;[109]=15;[110]=14;[111]=15;[112]=7;[113]=7;[114]=7;[115]=7;[116]=7;[117]=7;[118]=7;[119]=7;[120]=15;[121]=15;[122]=15;[123]=15;[124]=15;[125]=15;[126]=15;[127]=15;[128]=8;[129]=9;[130]=10;[131]=11;[132]=12;[133]=13;[134]=14;[135]=15;[136]=8;[137]=9;[138]=10;[139]=11;[140]=12;[141]=13;[142]=14;[143]=15;[144]=9;[145]=9;[146]=11;[147]=11;[148]=13;[149]=13;[150]=15;[151]=15;[152]=9;[153]=9;[154]=11;[155]=11;[156]=13;[157]=13;[158]=15;[159]=15;[160]=10;[161]=11;[162]=10;[163]=11;[164]=14;[165]=15;[166]=14;[167]=15;[168]=10;[169]=11;[170]=10;[171]=11;[172]=14;[173]=15;[174]=14;[175]=15;[176]=11;[177]=11;[178]=11;[179]=11;[180]=15;[181]=15;[182]=15;[183]=15;[184]=11;[185]=11;[186]=11;[187]=11;[188]=15;[189]=15;[190]=15;[191]=15;[192]=12;[193]=13;[194]=14;[195]=15;[196]=12;[197]=13;[198]=14;[199]=15;[200]=12;[201]=13;[202]=14;[203]=15;[204]=12;[205]=13;[206]=14;[207]=15;[208]=13;[209]=13;[210]=15;[211]=15;[212]=13;[213]=13;[214]=15;[215]=15;[216]=13;[217]=13;[218]=15;[219]=15;[220]=13;[221]=13;[222]=15;[223]=15;[224]=14;[225]=15;[226]=14;[227]=15;[228]=14;[229]=15;[230]=14;[231]=15;[232]=14;[233]=15;[234]=14;[235]=15;[236]=14;[237]=15;[238]=14;[239]=15;[240]=15;[241]=15;[242]=15;[243]=15;[244]=15;[245]=15;[246]=15;[247]=15;[248]=15;[249]=15;[250]=15;[251]=15;[252]=15;[253]=15;[254]=15;[255]=15;};
+local and_map = {[0]=0;[1]=0;[2]=0;[3]=0;[4]=0;[5]=0;[6]=0;[7]=0;[8]=0;[9]=0;[10]=0;[11]=0;[12]=0;[13]=0;[14]=0;[15]=0;[16]=0;[17]=1;[18]=0;[19]=1;[20]=0;[21]=1;[22]=0;[23]=1;[24]=0;[25]=1;[26]=0;[27]=1;[28]=0;[29]=1;[30]=0;[31]=1;[32]=0;[33]=0;[34]=2;[35]=2;[36]=0;[37]=0;[38]=2;[39]=2;[40]=0;[41]=0;[42]=2;[43]=2;[44]=0;[45]=0;[46]=2;[47]=2;[48]=0;[49]=1;[50]=2;[51]=3;[52]=0;[53]=1;[54]=2;[55]=3;[56]=0;[57]=1;[58]=2;[59]=3;[60]=0;[61]=1;[62]=2;[63]=3;[64]=0;[65]=0;[66]=0;[67]=0;[68]=4;[69]=4;[70]=4;[71]=4;[72]=0;[73]=0;[74]=0;[75]=0;[76]=4;[77]=4;[78]=4;[79]=4;[80]=0;[81]=1;[82]=0;[83]=1;[84]=4;[85]=5;[86]=4;[87]=5;[88]=0;[89]=1;[90]=0;[91]=1;[92]=4;[93]=5;[94]=4;[95]=5;[96]=0;[97]=0;[98]=2;[99]=2;[100]=4;[101]=4;[102]=6;[103]=6;[104]=0;[105]=0;[106]=2;[107]=2;[108]=4;[109]=4;[110]=6;[111]=6;[112]=0;[113]=1;[114]=2;[115]=3;[116]=4;[117]=5;[118]=6;[119]=7;[120]=0;[121]=1;[122]=2;[123]=3;[124]=4;[125]=5;[126]=6;[127]=7;[128]=0;[129]=0;[130]=0;[131]=0;[132]=0;[133]=0;[134]=0;[135]=0;[136]=8;[137]=8;[138]=8;[139]=8;[140]=8;[141]=8;[142]=8;[143]=8;[144]=0;[145]=1;[146]=0;[147]=1;[148]=0;[149]=1;[150]=0;[151]=1;[152]=8;[153]=9;[154]=8;[155]=9;[156]=8;[157]=9;[158]=8;[159]=9;[160]=0;[161]=0;[162]=2;[163]=2;[164]=0;[165]=0;[166]=2;[167]=2;[168]=8;[169]=8;[170]=10;[171]=10;[172]=8;[173]=8;[174]=10;[175]=10;[176]=0;[177]=1;[178]=2;[179]=3;[180]=0;[181]=1;[182]=2;[183]=3;[184]=8;[185]=9;[186]=10;[187]=11;[188]=8;[189]=9;[190]=10;[191]=11;[192]=0;[193]=0;[194]=0;[195]=0;[196]=4;[197]=4;[198]=4;[199]=4;[200]=8;[201]=8;[202]=8;[203]=8;[204]=12;[205]=12;[206]=12;[207]=12;[208]=0;[209]=1;[210]=0;[211]=1;[212]=4;[213]=5;[214]=4;[215]=5;[216]=8;[217]=9;[218]=8;[219]=9;[220]=12;[221]=13;[222]=12;[223]=13;[224]=0;[225]=0;[226]=2;[227]=2;[228]=4;[229]=4;[230]=6;[231]=6;[232]=8;[233]=8;[234]=10;[235]=10;[236]=12;[237]=12;[238]=14;[239]=14;[240]=0;[241]=1;[242]=2;[243]=3;[244]=4;[245]=5;[246]=6;[247]=7;[248]=8;[249]=9;[250]=10;[251]=11;[252]=12;[253]=13;[254]=14;[255]=15;}
+
+local not_map = {[0]=15;[1]=14;[2]=13;[3]=12;[4]=11;[5]=10;[6]=9;[7]=8;[8]=7;[9]=6;[10]=5;[11]=4;[12]=3;[13]=2;[14]=1;[15]=0;};
+local rshift1_map = {[0]=0;[1]=0;[2]=1;[3]=1;[4]=2;[5]=2;[6]=3;[7]=3;[8]=4;[9]=4;[10]=5;[11]=5;[12]=6;[13]=6;[14]=7;[15]=7;};
+local rshift1carry_map = {[0]=0;[1]=8;[2]=0;[3]=8;[4]=0;[5]=8;[6]=0;[7]=8;[8]=0;[9]=8;[10]=0;[11]=8;[12]=0;[13]=8;[14]=0;[15]=8;};
+local lshift1_map = {[0]=0;[1]=2;[2]=4;[3]=6;[4]=8;[5]=10;[6]=12;[7]=14;[8]=0;[9]=2;[10]=4;[11]=6;[12]=8;[13]=10;[14]=12;[15]=14;};
+local lshift1carry_map = {[0]=0;[1]=0;[2]=0;[3]=0;[4]=0;[5]=0;[6]=0;[7]=0;[8]=1;[9]=1;[10]=1;[11]=1;[12]=1;[13]=1;[14]=1;[15]=1;};
+local arshift1carry_map = {[0]=0;[1]=0;[2]=0;[3]=0;[4]=0;[5]=0;[6]=0;[7]=0;[8]=8;[9]=8;[10]=8;[11]=8;[12]=8;[13]=8;[14]=8;[15]=8;};
+
+module "bit"
+
+local bit_mt = {__tostring = function(t) return ("%x%x%x%x%x%x%x%x"):format(t[1],t[2],t[3],t[4],t[5],t[6],t[7],t[8]); end};
+local function do_bop(a, b, op)
+ return setmetatable({
+ op[a[1]*16+b[1]];
+ op[a[2]*16+b[2]];
+ op[a[3]*16+b[3]];
+ op[a[4]*16+b[4]];
+ op[a[5]*16+b[5]];
+ op[a[6]*16+b[6]];
+ op[a[7]*16+b[7]];
+ op[a[8]*16+b[8]];
+ }, bit_mt);
+end
+local function do_uop(a, op)
+ return setmetatable({
+ op[a[1]];
+ op[a[2]];
+ op[a[3]];
+ op[a[4]];
+ op[a[5]];
+ op[a[6]];
+ op[a[7]];
+ op[a[8]];
+ }, bit_mt);
+end
+
+function bxor(a, b) return do_bop(a, b, xor_map); end
+function bor(a, b) return do_bop(a, b, or_map); end
+function band(a, b) return do_bop(a, b, and_map); end
+
+function bnot(a) return do_uop(a, not_map); end
+local function _rshift1(t)
+ local carry = 0;
+ for i=1,8 do
+ local t_i = rshift1_map[t[i]] + carry;
+ carry = rshift1carry_map[t[i]];
+ t[i] = t_i;
+ end
+end
+function rshift(a, i)
+ local t = {a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]};
+ for n = 1,i do _rshift1(t); end
+ return setmetatable(t, bit_mt);
+end
+local function _arshift1(t)
+ local carry = arshift1carry_map[t[1]];
+ for i=1,8 do
+ local t_i = rshift1_map[t[i]] + carry;
+ carry = rshift1carry_map[t[i]];
+ t[i] = t_i;
+ end
+end
+function arshift(a, i)
+ local t = {a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]};
+ for n = 1,i do _arshift1(t); end
+ return setmetatable(t, bit_mt);
+end
+local function _lshift1(t)
+ local carry = 0;
+ for i=8,1,-1 do
+ local t_i = lshift1_map[t[i]] + carry;
+ carry = lshift1carry_map[t[i]];
+ t[i] = t_i;
+ end
+end
+function lshift(a, i)
+ local t = {a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]};
+ for n = 1,i do _lshift1(t); end
+ return setmetatable(t, bit_mt);
+end
+
+local function _cast(a)
+ if type(a) == "number" then a = ("%x"):format(a);
+ elseif type(a) == "table" then return a;
+ elseif type(a) ~= "string" then error("string expected, got "..type(a), 2); end
+ local t = {0,0,0,0,0,0,0,0};
+ a = "00000000"..a;
+ a = a:sub(-8);
+ for i = 1,8 do
+ t[i] = tonumber(a:sub(i,i), 16) or error("Number format error", 2);
+ end
+ return setmetatable(t, bit_mt);
+end
+
+local function wrap1(f)
+ return function(a, ...)
+ if type(a) ~= "table" then a = _cast(a); end
+ a = f(a, ...);
+ a = tonumber(tostring(a), 16);
+ if a > 0x7fffffff then a = a - 1 - 0xffffffff; end
+ return a;
+ end;
+end
+local function wrap2(f)
+ return function(a, b, ...)
+ if type(a) ~= "table" then a = _cast(a); end
+ if type(b) ~= "table" then b = _cast(b); end
+ a = f(a, b, ...);
+ a = tonumber(tostring(a), 16);
+ if a > 0x7fffffff then a = a - 1 - 0xffffffff; end
+ return a;
+ end;
+end
+
+bxor = wrap2(bxor);
+bor = wrap2(bor);
+band = wrap2(band);
+bnot = wrap1(bnot);
+lshift = wrap1(lshift);
+rshift = wrap1(rshift);
+arshift = wrap1(arshift);
+cast = wrap1(_cast);
+
+bits = 32;
+
+return _M;
diff --git a/net/http.lua b/net/http.lua
index 0910944e..b39cc6fb 100644
--- a/net/http.lua
+++ b/net/http.lua
@@ -9,8 +9,8 @@ local connlisteners_get = require "net.connlisteners".get;
local listener = connlisteners_get("httpclient") or error("No httpclient listener!");
local t_insert, t_concat = table.insert, table.concat;
-local tonumber, tostring, pairs, xpcall, select, debug_traceback =
- tonumber, tostring, pairs, xpcall, select, debug.traceback;
+local tonumber, tostring, pairs, xpcall, select, debug_traceback, char =
+ tonumber, tostring, pairs, xpcall, select, debug.traceback, string.char;
local log = require "util.logger".init("http");
local print = function () end
diff --git a/net/server.lua b/net/server.lua
index b2b19c70..9e42b8ff 100644
--- a/net/server.lua
+++ b/net/server.lua
@@ -791,7 +791,7 @@ local wrapclient = function( socket, ip, serverport, listeners, pattern, sslctx,
end
local addclient = function( address, port, listeners, pattern, sslctx, startssl )
- local client, err = socket.tcp( )
+ local client, err = luasocket.tcp( )
if err then
return nil, err
end
@@ -800,7 +800,7 @@ local addclient = function( address, port, listeners, pattern, sslctx, startssl
if err then -- try again
local handler = wrapclient( client, address, port, listeners )
else
- wrapconnection( server, listeners, socket, address, port, "clientport", pattern, sslctx, startssl )
+ wrapconnection( nil, listeners, client, address, port, "clientport", pattern, sslctx, startssl )
end
end
diff --git a/net/xmppclient_listener.lua b/net/xmppclient_listener.lua
index 44852834..dbb87b91 100644
--- a/net/xmppclient_listener.lua
+++ b/net/xmppclient_listener.lua
@@ -9,6 +9,7 @@
local logger = require "logger";
+local log = logger.init("xmppclient_listener");
local lxp = require "lxp"
local init_xmlhandlers = require "core.xmlhandlers"
local sm_new_session = require "core.sessionmanager".new_session;
@@ -20,10 +21,11 @@ local t_concat = table.concat;
local t_concatall = function (t, sep) local tt = {}; for _, s in ipairs(t) do t_insert(tt, tostring(s)); end return t_concat(tt, sep); end
local m_random = math.random;
local format = string.format;
-local sm_new_session, sm_destroy_session = sessionmanager.new_session, sessionmanager.destroy_session; --import("core.sessionmanager", "new_session", "destroy_session");
+local sessionmanager = require "core.sessionmanager";
+local sm_new_session, sm_destroy_session = sessionmanager.new_session, sessionmanager.destroy_session;
local sm_streamopened = sessionmanager.streamopened;
local sm_streamclosed = sessionmanager.streamclosed;
-local st = stanza;
+local st = require "util.stanza";
local stream_callbacks = { stream_tag = "http://etherx.jabber.org/streams|stream",
default_ns = "jabber:client",
diff --git a/net/xmppserver_listener.lua b/net/xmppserver_listener.lua
index 0216e8dc..ddfb185e 100644
--- a/net/xmppserver_listener.lua
+++ b/net/xmppserver_listener.lua
@@ -9,9 +9,9 @@
local logger = require "logger";
+local log = logger.init("xmppserver_listener");
local lxp = require "lxp"
local init_xmlhandlers = require "core.xmlhandlers"
-local sm_new_session = require "core.sessionmanager".new_session;
local s2s_new_incoming = require "core.s2smanager".new_incoming;
local s2s_streamopened = require "core.s2smanager".streamopened;
local s2s_streamclosed = require "core.s2smanager".streamclosed;
@@ -42,8 +42,9 @@ local t_concat = table.concat;
local t_concatall = function (t, sep) local tt = {}; for _, s in ipairs(t) do t_insert(tt, tostring(s)); end return t_concat(tt, sep); end
local m_random = math.random;
local format = string.format;
-local sm_new_session, sm_destroy_session = sessionmanager.new_session, sessionmanager.destroy_session; --import("core.sessionmanager", "new_session", "destroy_session");
-local st = stanza;
+local sessionmanager = require "core.sessionmanager";
+local sm_new_session, sm_destroy_session = sessionmanager.new_session, sessionmanager.destroy_session;
+local st = require "util.stanza";
local sessions = {};
local xmppserver = { default_port = 5269, default_mode = "*a" };
diff --git a/plugins/mod_bosh.lua b/plugins/mod_bosh.lua
index 5aa8ff34..46e9a95f 100644
--- a/plugins/mod_bosh.lua
+++ b/plugins/mod_bosh.lua
@@ -1,6 +1,7 @@
module.host = "*" -- Global module
+local hosts = _G.hosts;
local lxp = require "lxp";
local init_xmlhandlers = require "core.xmlhandlers"
local server = require "net.server";
diff --git a/plugins/mod_muc.lua b/plugins/mod_muc.lua
index ffa0bb85..38f21b4a 100644
--- a/plugins/mod_muc.lua
+++ b/plugins/mod_muc.lua
@@ -205,6 +205,34 @@ function broadcast_presence_stanza(room, stanza, code, nick)
end
end
end
+function send_history(room, to)
+ local history = rooms_info:get(room, 'history'); -- send discussion history
+ if history then
+ for _, msg in ipairs(history) do
+ msg = st.deserialize(msg);
+ msg.attr.to=to;
+ core_route_stanza(component, msg);
+ end
+ end
+ if rooms_info:get(room, 'subject') then
+ core_route_stanza(component, st.message({type='groupchat', from=room, to=to}):tag("subject"):text(rooms_info:get(room, 'subject')));
+ end
+end
+function send_occupant_list(room, to)
+ local r = rooms:get(room);
+ if r then
+ local current_nick = jid_nick:get(to, room);
+ for occupant, o_data in pairs(r) do
+ if occupant ~= current_nick then
+ local pres = get_filtered_presence(o_data.sessions[o_data.jid]);
+ pres.attr.to, pres.attr.from = to, occupant;
+ pres:tag("x", {xmlns='http://jabber.org/protocol/muc#user'})
+ :tag("item", {affiliation=o_data.affiliation, role=o_data.role}):up();
+ core_route_stanza(component, pres);
+ end
+ end
+ end
+end
function handle_to_occupant(origin, stanza) -- PM, vCards, etc
local from, to = stanza.attr.from, stanza.attr.to;
@@ -232,7 +260,7 @@ function handle_to_occupant(origin, stanza) -- PM, vCards, etc
end
elseif not type then -- available
if current_nick then
- if #pr == #stanza or current_nick ~= to then
+ --if #pr == #stanza or current_nick ~= to then -- commented because google keeps resending directed presence
if current_nick == to then -- simple presence
log("debug", "%s broadcasted presence", current_nick);
rooms:get(room, current_nick).sessions[from] = pr;
@@ -259,11 +287,11 @@ function handle_to_occupant(origin, stanza) -- PM, vCards, etc
end
end
end
- else -- possible rejoin
- log("debug", "%s had connection replaced", current_nick);
- handle_to_occupant(origin, st.presence({type='unavailable', from=from, to=to}):tag('status'):text('Replaced by new connection'):up()); -- send unavailable
- handle_to_occupant(origin, stanza); -- resend available
- end
+ --else -- possible rejoin
+ -- log("debug", "%s had connection replaced", current_nick);
+ -- handle_to_occupant(origin, st.presence({type='unavailable', from=from, to=to}):tag('status'):text('Replaced by new connection'):up()); -- send unavailable
+ -- handle_to_occupant(origin, stanza); -- resend available
+ --end
else -- enter room
local new_nick = to;
if rooms:get(room, to) then
@@ -284,31 +312,10 @@ function handle_to_occupant(origin, stanza) -- PM, vCards, etc
end
rooms:set(room, to, data);
jid_nick:set(from, room, to);
- local r = rooms:get(room);
- if r then
- for occupant, o_data in pairs(r) do
- if occupant ~= to then
- local pres = get_filtered_presence(o_data.sessions[o_data.jid]);
- pres.attr.to, pres.attr.from = from, occupant;
- pres:tag("x", {xmlns='http://jabber.org/protocol/muc#user'})
- :tag("item", {affiliation=o_data.affiliation, role=o_data.role}):up();
- core_route_stanza(component, pres);
- end
- end
- end
+ send_occupant_list(room, from);
pr.attr.from = to;
broadcast_presence_stanza(room, pr);
- local history = rooms_info:get(room, 'history'); -- send discussion history
- if history then
- for _, msg in ipairs(history) do
- msg = st.deserialize(msg);
- msg.attr.to=from;
- core_route_stanza(component, msg);
- end
- end
- if rooms_info:get(room, 'subject') then
- core_route_stanza(component, st.message({type='groupchat', from=room, to=from}):tag("subject"):text(rooms_info:get(room, 'subject')));
- end
+ send_history(room, from);
end
end
elseif type ~= 'result' then -- bad type
diff --git a/plugins/mod_pep.lua b/plugins/mod_pep.lua
new file mode 100644
index 00000000..438b077d
--- /dev/null
+++ b/plugins/mod_pep.lua
@@ -0,0 +1,48 @@
+
+local jid_bare = require "util.jid".bare;
+local jid_split = require "util.jid".split;
+local st = require "util.stanza";
+local hosts = hosts;
+local user_exists = require "core.usermanager".user_exists;
+local is_contact_subscribed = require "core.rostermanager".is_contact_subscribed;
+local pairs, ipairs = pairs, ipairs;
+
+local function publish(session, node, item)
+ local stanza = st.message({from=session.full_jid, type='headline'})
+ :tag('event', {xmlns='http://jabber.org/protocol/pubsub#event'})
+ :tag('items', {node=node})
+ :add_child(item)
+ :up()
+ :up();
+
+ -- broadcast to resources
+ stanza.attr.to = session.username..'@'..session.host;
+ core_route_stanza(session, stanza);
+
+ -- broadcast to contacts
+ for jid, item in pairs(session.roster) do
+ if jid and jid ~= "pending" and (item.subscription == 'from' or item.subscription == 'both') then
+ stanza.attr.to = jid;
+ core_route_stanza(session, stanza);
+ end
+ end
+end
+
+module:add_iq_handler("c2s", "http://jabber.org/protocol/pubsub", function (session, stanza)
+ if stanza.attr.type == 'set' and (not stanza.attr.to or jid_bare(stanza.attr.from) == stanza.attr.to) then
+ local payload = stanza.tags[1];
+ if payload.name == 'pubsub' then
+ payload = payload.tags[1];
+ if payload and payload.name == 'publish' and payload.attr.node then
+ local node = payload.attr.node;
+ payload = payload.tags[1];
+ if payload then
+ publish(session, node, payload);
+ return true;
+ end -- TODO else error
+ end -- TODO else error
+ end
+ end
+ origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
+end);
+
diff --git a/plugins/mod_presence.lua b/plugins/mod_presence.lua
new file mode 100644
index 00000000..7c34eefa
--- /dev/null
+++ b/plugins/mod_presence.lua
@@ -0,0 +1,280 @@
+-- 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.
+--
+
+
+
+local log = require "util.logger".init("mod_presence")
+
+local require = require;
+local pairs, ipairs = pairs, ipairs;
+local t_concat = table.concat;
+local s_find = string.find;
+local tonumber = tonumber;
+
+local st = require "util.stanza";
+local jid_split = require "util.jid".split;
+local jid_bare = require "util.jid".bare;
+local hosts = hosts;
+
+local rostermanager = require "core.rostermanager";
+local sessionmanager = require "core.sessionmanager";
+local offlinemanager = require "core.offlinemanager";
+
+local _core_route_stanza = core_route_stanza;
+local core_route_stanza;
+function core_route_stanza(origin, stanza)
+ if stanza.attr.type ~= nil and stanza.attr.type ~= "unavailable" and stanza.attr.type ~= "error" then
+ local node, host = jid_split(stanza.attr.to);
+ host = hosts[host];
+ if host and host.type == "local" then
+ handle_inbound_presence_subscriptions_and_probes(origin, stanza, jid_bare(stanza.attr.from), jid_bare(stanza.attr.to), core_route_stanza);
+ return;
+ end
+ end
+ _core_route_stanza(origin, stanza);
+end
+
+function handle_presence(origin, stanza, from_bare, to_bare, core_route_stanza, inbound)
+ local type = stanza.attr.type;
+ if type and type ~= "unavailable" and type ~= "error" then
+ if inbound then
+ handle_inbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare, core_route_stanza);
+ else
+ handle_outbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare, core_route_stanza);
+ end
+ elseif not inbound and not stanza.attr.to then
+ handle_normal_presence(origin, stanza, core_route_stanza);
+ else
+ core_route_stanza(origin, stanza);
+ end
+end
+
+function handle_normal_presence(origin, stanza, core_route_stanza)
+ if origin.roster then
+ for jid in pairs(origin.roster) do -- broadcast to all interested contacts
+ local subscription = origin.roster[jid].subscription;
+ if subscription == "both" or subscription == "from" then
+ stanza.attr.to = jid;
+ core_route_stanza(origin, stanza);
+ end
+ end
+ local node, host = jid_split(stanza.attr.from);
+ for _, res in pairs(hosts[host].sessions[node].sessions) do -- broadcast to all resources
+ if res ~= origin and res.presence then -- to resource
+ stanza.attr.to = res.full_jid;
+ core_route_stanza(origin, stanza);
+ end
+ end
+ if stanza.attr.type == nil and not origin.presence then -- initial presence
+ local probe = st.presence({from = origin.full_jid, type = "probe"});
+ for jid in pairs(origin.roster) do -- probe all contacts we are subscribed to
+ local subscription = origin.roster[jid].subscription;
+ if subscription == "both" or subscription == "to" then
+ probe.attr.to = jid;
+ core_route_stanza(origin, probe);
+ end
+ end
+ for _, res in pairs(hosts[host].sessions[node].sessions) do -- broadcast from all available resources
+ if res ~= origin and res.presence then
+ res.presence.attr.to = origin.full_jid;
+ core_route_stanza(res, res.presence);
+ res.presence.attr.to = nil;
+ end
+ end
+ if origin.roster.pending then -- resend incoming subscription requests
+ for jid in pairs(origin.roster.pending) do
+ origin.send(st.presence({type="subscribe", from=jid})); -- TODO add to attribute? Use original?
+ end
+ end
+ local request = st.presence({type="subscribe", from=origin.username.."@"..origin.host});
+ for jid, item in pairs(origin.roster) do -- resend outgoing subscription requests
+ if item.ask then
+ request.attr.to = jid;
+ core_route_stanza(origin, request);
+ end
+ end
+ local offline = offlinemanager.load(node, host);
+ if offline then
+ for _, msg in ipairs(offline) do
+ origin.send(msg); -- FIXME do we need to modify to/from in any way?
+ end
+ offlinemanager.deleteAll(node, host);
+ end
+ end
+ origin.priority = 0;
+ if stanza.attr.type == "unavailable" then
+ origin.presence = nil;
+ if origin.directed then
+ local old_from = stanza.attr.from;
+ stanza.attr.from = origin.full_jid;
+ for jid in pairs(origin.directed) do
+ stanza.attr.to = jid;
+ core_route_stanza(origin, stanza);
+ end
+ stanza.attr.from = old_from;
+ origin.directed = nil;
+ end
+ else
+ origin.presence = stanza;
+ local priority = stanza:child_with_name("priority");
+ if priority and #priority > 0 then
+ priority = t_concat(priority);
+ if s_find(priority, "^[+-]?[0-9]+$") then
+ priority = tonumber(priority);
+ if priority < -128 then priority = -128 end
+ if priority > 127 then priority = 127 end
+ origin.priority = priority;
+ end
+ end
+ end
+ stanza.attr.to = nil; -- reset it
+ else
+ log("error", "presence recieved from client with no roster");
+ end
+end
+
+function send_presence_of_available_resources(user, host, jid, recipient_session, core_route_stanza)
+ local h = hosts[host];
+ local count = 0;
+ if h and h.type == "local" then
+ local u = h.sessions[user];
+ if u then
+ for k, session in pairs(u.sessions) do
+ local pres = session.presence;
+ if pres then
+ pres.attr.to = jid;
+ pres.attr.from = session.full_jid;
+ core_route_stanza(session, pres);
+ pres.attr.to = nil;
+ pres.attr.from = nil;
+ count = count + 1;
+ end
+ end
+ end
+ end
+ log("info", "broadcasted presence of "..count.." resources from "..user.."@"..host.." to "..jid);
+ return count;
+end
+
+function handle_outbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare, core_route_stanza)
+ local node, host = jid_split(from_bare);
+ local st_from, st_to = stanza.attr.from, stanza.attr.to;
+ stanza.attr.from, stanza.attr.to = from_bare, to_bare;
+ log("debug", "outbound presence "..stanza.attr.type.." from "..from_bare.." for "..to_bare);
+ if stanza.attr.type == "subscribe" then
+ -- 1. route stanza
+ -- 2. roster push (subscription = none, ask = subscribe)
+ if rostermanager.set_contact_pending_out(node, host, to_bare) then
+ rostermanager.roster_push(node, host, to_bare);
+ end -- else file error
+ core_route_stanza(origin, stanza);
+ elseif stanza.attr.type == "unsubscribe" then
+ -- 1. route stanza
+ -- 2. roster push (subscription = none or from)
+ if rostermanager.unsubscribe(node, host, to_bare) then
+ rostermanager.roster_push(node, host, to_bare); -- FIXME do roster push when roster has in fact not changed?
+ end -- else file error
+ core_route_stanza(origin, stanza);
+ elseif stanza.attr.type == "subscribed" then
+ -- 1. route stanza
+ -- 2. roster_push ()
+ -- 3. send_presence_of_available_resources
+ if rostermanager.subscribed(node, host, to_bare) then
+ rostermanager.roster_push(node, host, to_bare);
+ end
+ core_route_stanza(origin, stanza);
+ send_presence_of_available_resources(node, host, to_bare, origin, core_route_stanza);
+ elseif stanza.attr.type == "unsubscribed" then
+ -- 1. route stanza
+ -- 2. roster push (subscription = none or to)
+ if rostermanager.unsubscribed(node, host, to_bare) then
+ rostermanager.roster_push(node, host, to_bare);
+ end
+ core_route_stanza(origin, stanza);
+ end
+ stanza.attr.from, stanza.attr.to = st_from, st_to;
+end
+
+function handle_inbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare, core_route_stanza)
+ local node, host = jid_split(to_bare);
+ local st_from, st_to = stanza.attr.from, stanza.attr.to;
+ stanza.attr.from, stanza.attr.to = from_bare, to_bare;
+ log("debug", "inbound presence "..stanza.attr.type.." from "..from_bare.." for "..to_bare);
+ if stanza.attr.type == "probe" then
+ if rostermanager.is_contact_subscribed(node, host, from_bare) then
+ 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(origin, 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
+ -- 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
+ 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);
+ end -- TODO else return error, unable to save
+ end
+ end
+ elseif stanza.attr.type == "unsubscribe" then
+ if rostermanager.process_inbound_unsubscribe(node, host, from_bare) then
+ 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
+ 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
+ rostermanager.roster_push(node, host, from_bare);
+ end
+ end -- discard any other type
+ stanza.attr.from, stanza.attr.to = st_from, st_to;
+end
+
+local function presence_handler(data)
+ local origin, stanza = data.origin, data.stanza;
+ local to = stanza.attr.to;
+ local node, host = jid_split(to);
+ local to_bare = jid_bare(to);
+ local from_bare = jid_bare(stanza.attr.from);
+ if origin.type == "c2s" then
+ if to ~= nil and not(origin.roster[to_bare] and (origin.roster[to_bare].subscription == "both" or origin.roster[to_bare].subscription == "from")) then -- directed presence
+ origin.directed = origin.directed or {};
+ origin.directed[to] = true; -- FIXME does it make more sense to add to_bare rather than to?
+ end
+ if stanza.attr.type ~= nil and stanza.attr.type ~= "unavailable" and stanza.attr.type ~= "error" then
+ handle_outbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare, core_route_stanza);
+ elseif not to then
+ handle_normal_presence(origin, stanza, core_route_stanza);
+ else
+ core_route_stanza(origin, stanza);
+ end
+ elseif (origin.type == "s2sin" or origin.type == "component") and hosts[host] then
+ if stanza.attr.type ~= nil and stanza.attr.type ~= "unavailable" and stanza.attr.type ~= "error" then
+ handle_inbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare, core_route_stanza);
+ else
+ core_route_stanza(origin, stanza);
+ end
+ end
+ return true;
+end
+
+local add_handler = require "core.eventmanager2".add_handler;
+local remove_handler = require "core.eventmanager2".remove_handler;
+
+add_handler(module:get_host().."/presence", presence_handler);
+module.unload = function()
+ remove_handler(module:get_host().."/presence", presence_handler);
+end
diff --git a/plugins/mod_register.lua b/plugins/mod_register.lua
index 14c00767..43124ff9 100644
--- a/plugins/mod_register.lua
+++ b/plugins/mod_register.lua
@@ -60,6 +60,8 @@ module:add_iq_handler("c2s", "jabber:iq:register", function (session, stanza)
end
end
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 });
else
local username = query:child_with_name("username");
local password = query:child_with_name("password");
@@ -143,6 +145,10 @@ module:add_iq_handler("c2s_unauthed", "jabber:iq:register", function (session, s
else
if usermanager_create_user(username, password, session.host) then
session.send(st.reply(stanza)); -- user created!
+ module:log("info", "User account created: %s@%s", username, session.host);
+ module:fire_event("user-registered", {
+ username = username, host = session.host, source = "mod_register",
+ session = session });
else
-- TODO unable to write file, file may be locked, etc, what's the correct error?
session.send(st.error_reply(stanza, "wait", "internal-server-error"));
diff --git a/plugins/mod_roster.lua b/plugins/mod_roster.lua
index e30bc1f9..554ef2e5 100644
--- a/plugins/mod_roster.lua
+++ b/plugins/mod_roster.lua
@@ -23,6 +23,14 @@ local core_route_stanza = core_route_stanza;
module:add_feature("jabber:iq:roster");
+local rosterver_stream_feature = st.stanza("ver", {xmlns="urn:xmpp:features:rosterver"}):tag("optional"):up();
+module:add_event_hook("stream-features",
+ function (session, features)
+ if session.username then
+ features:add_child(rosterver_stream_feature);
+ end
+ end);
+
module:add_iq_handler("c2s", "jabber:iq:roster",
function (session, stanza)
if stanza.tags[1].name == "query" then
diff --git a/plugins/mod_welcome.lua b/plugins/mod_welcome.lua
new file mode 100644
index 00000000..491acb8f
--- /dev/null
+++ b/plugins/mod_welcome.lua
@@ -0,0 +1,23 @@
+-- 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.
+--
+
+local config = require "core.configmanager";
+
+local host = module:get_host();
+local welcome_text = config.get("*", "core", "welcome_message") or "Hello $user, welcome to the $host IM server!";
+
+local st = require "util.stanza";
+
+module:add_event_hook("user-registered",
+ function (user)
+ local welcome_stanza =
+ st.message({ to = user.username.."@"..user.host, from = host })
+ :tag("body"):text(welcome_text:gsub("$(%w+)", user));
+ core_route_stanza(hosts[host], welcome_stanza);
+ module:log("debug", "Welcomed user %s@%s", user.username, user.host);
+ end);
diff --git a/prosody.cfg.lua.dist b/prosody.cfg.lua.dist
index dc636192..182f8889 100644
--- a/prosody.cfg.lua.dist
+++ b/prosody.cfg.lua.dist
@@ -15,7 +15,7 @@
-- A table is a list of values, except each value has a name. An
-- example would be:
--
--- logging = { type = "html", directory = "/var/logs", rotate = "daily" }
+-- ssl = { key = "keyfile.key", certificate = "certificate.cert" }
--
-- Whitespace (that is tabs, spaces, line breaks) is mostly insignificant, so
-- can
@@ -38,6 +38,7 @@ Host "*"
modules_enabled = {
-- Generally required
"roster"; -- Allow users to have a roster. Recommended ;)
+ "presence"; -- See and broadcast status changes to/from contacts
"saslauth"; -- Authentication for clients and servers. Recommended if you want to log in.
"tls"; -- Add support for secure TLS on c2s/s2s connections
"dialback"; -- s2s dialback support
diff --git a/util/events.lua b/util/events.lua
index b1f3811c..3816f30b 100644
--- a/util/events.lua
+++ b/util/events.lua
@@ -2,6 +2,7 @@
local ipairs = ipairs;
local pairs = pairs;
local t_insert = table.insert;
+local t_sort = table.sort;
local select = select;
module "events"
@@ -10,32 +11,32 @@ function new()
local dispatchers = {};
local handlers = {};
local event_map = {};
- local function _rebuild_index() -- TODO optimize index rebuilding
- for event, _handlers in pairs(event_map) do
- local index = handlers[event];
- if index then
- for i=#index,1,-1 do index[i] = nil; end
- else index = {}; handlers[event] = index; end
- for handler in pairs(_handlers) do
- t_insert(index, handler);
- end
+ local function _rebuild_index(event) -- TODO optimize index rebuilding
+ local _handlers = event_map[event];
+ local index = handlers[event];
+ if index then
+ for i=#index,1,-1 do index[i] = nil; end
+ else index = {}; handlers[event] = index; end
+ for handler in pairs(_handlers) do
+ t_insert(index, handler);
end
+ t_sort(index, function(a, b) return _handlers[a] > _handlers[b]; end);
end;
- local function add_handler(event, handler)
+ local function add_handler(event, handler, priority)
local map = event_map[event];
if map then
- map[handler] = true;
+ map[handler] = priority or 0;
else
- map = {[handler] = true};
+ map = {[handler] = priority or 0};
event_map[event] = map;
end
- _rebuild_index();
+ _rebuild_index(event);
end;
local function remove_handler(event, handler)
local map = event_map[event];
if map then
map[handler] = nil;
- _rebuild_index();
+ _rebuild_index(event);
end
end;
local function add_plugin(plugin)
@@ -51,9 +52,10 @@ function new()
local function _create_dispatcher(event) -- FIXME duplicate code in fire_event
local h = handlers[event];
if not h then h = {}; handlers[event] = h; end
- local dispatcher = function(data)
+ local dispatcher = function(...)
for _, handler in ipairs(h) do
- handler(data);
+ local ret = handler(...);
+ if ret ~= nil then return ret; end
end
end;
dispatchers[event] = dispatcher;
@@ -62,11 +64,12 @@ function new()
local function get_dispatcher(event)
return dispatchers[event] or _create_dispatcher(event);
end;
- local function fire_event(event, data) -- FIXME duplicates dispatcher code
+ local function fire_event(event, ...) -- FIXME duplicates dispatcher code
local h = handlers[event];
if h then
for _, handler in ipairs(h) do
- handler(data);
+ local ret = handler(...);
+ if ret ~= nil then return ret; end
end
end
end;
diff --git a/util/jid.lua b/util/jid.lua
index b6baf9dd..4f8b6d41 100644
--- a/util/jid.lua
+++ b/util/jid.lua
@@ -15,7 +15,7 @@ local resourceprep = require "util.encodings".stringprep.resourceprep;
module "jid"
-function split(jid)
+local function _split(jid)
if not jid then return; end
local node, nodepos = match(jid, "^([^@]+)@()");
local host, hostpos = match(jid, "^([^@/]+)()", nodepos)
@@ -24,17 +24,18 @@ function split(jid)
if (not host) or ((not resource) and #jid >= hostpos) then return nil, nil, nil; end
return node, host, resource;
end
+split = _split;
function bare(jid)
- local node, host = split(jid);
+ local node, host = _split(jid);
if node and host then
return node.."@"..host;
end
return host;
end
-function prepped_split(jid)
- local node, host, resource = split(jid);
+local function _prepped_split(jid)
+ local node, host, resource = _split(jid);
if host then
host = nameprep(host);
if not host then return; end
@@ -49,9 +50,10 @@ function prepped_split(jid)
return node, host, resource;
end
end
+prepped_split = _prepped_split;
function prep(jid)
- local node, host, resource = prepped_split(jid);
+ local node, host, resource = _prepped_split(jid);
if host then
if node then
host = node .. "@" .. host;
diff --git a/util/muc.lua b/util/muc.lua
new file mode 100644
index 00000000..305157d8
--- /dev/null
+++ b/util/muc.lua
@@ -0,0 +1,422 @@
+-- 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.
+--
+
+local datamanager = require "util.datamanager";
+local datetime = require "util.datetime";
+
+local jid_split = require "util.jid".split;
+local jid_bare = require "util.jid".bare;
+local st = require "util.stanza";
+local log = require "util.logger".init("mod_muc");
+local multitable_new = require "util.multitable".new;
+local t_insert, t_remove = table.insert, table.remove;
+
+local muc_domain = nil; --module:get_host();
+local history_length = 20;
+
+------------
+local function filter_xmlns_from_array(array, filters)
+ local count = 0;
+ for i=#array,1,-1 do
+ local attr = array[i].attr;
+ if filters[attr and attr.xmlns] then
+ t_remove(array, i);
+ count = count + 1;
+ end
+ end
+ return count;
+end
+local function filter_xmlns_from_stanza(stanza, filters)
+ if filters then
+ if filter_xmlns_from_array(stanza.tags, filters) ~= 0 then
+ return stanza, filter_xmlns_from_array(stanza, filters);
+ end
+ end
+ return stanza, 0;
+end
+local presence_filters = {["http://jabber.org/protocol/muc"]=true;["http://jabber.org/protocol/muc#user"]=true};
+local function get_filtered_presence(stanza)
+ return filter_xmlns_from_stanza(st.clone(stanza), presence_filters);
+end
+local kickable_error_conditions = {
+ ["gone"] = true;
+ ["internal-server-error"] = true;
+ ["item-not-found"] = true;
+ ["jid-malformed"] = true;
+ ["recipient-unavailable"] = true;
+ ["redirect"] = true;
+ ["remote-server-not-found"] = true;
+ ["remote-server-timeout"] = true;
+ ["service-unavailable"] = true;
+};
+local function get_kickable_error(stanza)
+ for _, tag in ipairs(stanza.tags) do
+ if tag.name == "error" and tag.attr.xmlns == "jabber:client" then
+ for _, cond in ipairs(tag.tags) do
+ if cond.attr.xmlns == "urn:ietf:params:xml:ns:xmpp-stanzas" then
+ return kickable_error_conditions[cond.name] and cond.name;
+ end
+ end
+ return true; -- malformed error message
+ end
+ end
+ return true; -- malformed error message
+end
+local function getUsingPath(stanza, path, getText)
+ local tag = stanza;
+ for _, name in ipairs(path) do
+ if type(tag) ~= 'table' then return; end
+ tag = tag:child_with_name(name);
+ end
+ if tag and getText then tag = table.concat(tag); end
+ return tag;
+end
+local function getTag(stanza, path) return getUsingPath(stanza, path); end
+local function getText(stanza, path) return getUsingPath(stanza, path, true); end
+-----------
+
+--[[function get_room_disco_info(room, stanza)
+ return st.iq({type='result', id=stanza.attr.id, from=stanza.attr.to, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#info")
+ :tag("identity", {category='conference', type='text', name=room._data["name"]):up()
+ :tag("feature", {var="http://jabber.org/protocol/muc"}); -- TODO cache disco reply
+end
+function get_room_disco_items(room, stanza)
+ return st.iq({type='result', id=stanza.attr.id, from=stanza.attr.to, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#items");
+end -- TODO allow non-private rooms]]
+
+--
+
+local function room_broadcast_presence(room, stanza, code, nick)
+ stanza = get_filtered_presence(stanza);
+ local data = room._participants[stanza.attr.from];
+ stanza:tag("x", {xmlns='http://jabber.org/protocol/muc#user'})
+ :tag("item", {affiliation=data.affiliation, role=data.role, nick=nick}):up();
+ if code then
+ stanza:tag("status", {code=code}):up();
+ end
+ local me;
+ for occupant, o_data in pairs(room._participants) do
+ if occupant ~= stanza.attr.from then
+ for jid in pairs(o_data.sessions) do
+ stanza.attr.to = jid;
+ room:route_stanza(stanza);
+ end
+ else
+ me = o_data;
+ end
+ end
+ if me then
+ stanza:tag("status", {code='110'});
+ for jid in pairs(me.sessions) do
+ stanza.attr.to = jid;
+ room:route_stanza(stanza);
+ end
+ end
+end
+local function room_broadcast_message(room, stanza, historic)
+ for occupant, o_data in pairs(room._participants) do
+ for jid in pairs(o_data.sessions) do
+ stanza.attr.to = jid;
+ room:route_stanza(stanza);
+ end
+ end
+ if historic then -- add to history
+ local history = room._data['history'];
+ if not history then history = {}; room._data['history'] = history; end
+ -- 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)));
+ while #history > history_length do t_remove(history, 1) end
+ end
+end
+
+
+local function room_send_occupant_list(room, to)
+ local current_nick = room._jid_nick[to];
+ for occupant, o_data in pairs(room._participants) do
+ if occupant ~= current_nick then
+ local pres = get_filtered_presence(o_data.sessions[o_data.jid]);
+ pres.attr.to, pres.attr.from = to, occupant;
+ pres:tag("x", {xmlns='http://jabber.org/protocol/muc#user'})
+ :tag("item", {affiliation=o_data.affiliation, role=o_data.role}):up();
+ room:route_stanza(pres);
+ end
+ end
+end
+local function room_send_history(room, to)
+ local history = room._data['history']; -- send discussion history
+ if history then
+ for _, msg in ipairs(history) do
+ msg = st.deserialize(msg);
+ msg.attr.to=to;
+ room:route_stanza(msg);
+ end
+ end
+ if room._data['subject'] then
+ room:route_stanza(st.message({type='groupchat', from=room, to=to}):tag("subject"):text(room._data['subject']));
+ end
+end
+
+local function room_get_disco_info(self, stanza) end
+local function room_get_disco_items(self, stanza) end
+local function room_set_subject(room, current_nick, room, subject)
+ -- TODO check nick's authority
+ if subject == "" then subject = nil; end
+ room._data['subject'] = subject;
+ local msg = st.message({type='groupchat', from=current_nick})
+ :tag('subject'):text(subject):up();
+ room_broadcast_message(room, msg, false);
+ return true;
+end
+
+local function room_handle_to_occupant(self, origin, stanza) -- PM, vCards, etc
+ local from, to = stanza.attr.from, stanza.attr.to;
+ local room = jid_bare(to);
+ local current_nick = self._jid_nick[from];
+ local type = stanza.attr.type;
+ log("debug", "room: %s, current_nick: %s, stanza: %s", room or "nil", current_nick or "nil", stanza:top_tag());
+ if (select(2, jid_split(from)) == muc_domain) then error("Presence from the MUC itself!!!"); end
+ if stanza.name == "presence" then
+ local pr = get_filtered_presence(stanza);
+ pr.attr.from = current_nick;
+ if type == "error" then -- error, kick em out!
+ if current_nick then
+ log("debug", "kicking %s from %s", current_nick, room);
+ room_handle_to_occupant(self, origin, st.presence({type='unavailable', from=from, to=to}):tag('status'):text('This participant is kicked from the room because he sent an error presence')); -- send unavailable
+ end
+ elseif type == "unavailable" then -- unavailable
+ if current_nick then
+ log("debug", "%s leaving %s", current_nick, room);
+ local data = self._participants[current_nick];
+ data.role = 'none';
+ room_broadcast_presence(room, pr);
+ self._participants[current_nick] = nil;
+ self._jid_nick[from] = nil;
+ end
+ elseif not type then -- available
+ if current_nick then
+ --if #pr == #stanza or current_nick ~= to then -- commented because google keeps resending directed presence
+ if current_nick == to then -- simple presence
+ log("debug", "%s broadcasted presence", current_nick);
+ self._participants[current_nick].sessions[from] = pr;
+ room_broadcast_presence(self, pr);
+ else -- change nick
+ if self._participants[to] then
+ log("debug", "%s couldn't change nick", current_nick);
+ origin.send(st.error_reply(stanza, "cancel", "conflict"));
+ else
+ local data = self._participants[current_nick];
+ local to_nick = select(3, jid_split(to));
+ if to_nick then
+ log("debug", "%s (%s) changing nick to %s", current_nick, data.jid, to);
+ local p = st.presence({type='unavailable', from=current_nick});
+ room_broadcast_presence(self, p, '303', to_nick);
+ self._participants[current_nick] = nil;
+ self._participants[to] = data;
+ self._jid_nick[from] = to;
+ pr.attr.from = to;
+ self._participants[to].sessions[from] = pr;
+ room_broadcast_presence(self, pr);
+ else
+ --TODO malformed-jid
+ end
+ end
+ end
+ --else -- possible rejoin
+ -- log("debug", "%s had connection replaced", current_nick);
+ -- handle_to_occupant(origin, st.presence({type='unavailable', from=from, to=to}):tag('status'):text('Replaced by new connection'):up()); -- send unavailable
+ -- handle_to_occupant(origin, stanza); -- resend available
+ --end
+ else -- enter room
+ local new_nick = to;
+ if self._participants[to] then
+ new_nick = nil;
+ end
+ if not new_nick then
+ log("debug", "%s couldn't join due to nick conflict: %s", from, to);
+ origin.send(st.error_reply(stanza, "cancel", "conflict"));
+ else
+ log("debug", "%s joining as %s", from, to);
+ local data;
+-- if not rooms:get(room) and not rooms_info:get(room) then -- new room
+-- rooms_info:set(room, 'name', (jid_split(room)));
+-- data = {affiliation='owner', role='moderator', jid=from, sessions={[from]=get_filtered_presence(stanza)}};
+-- end
+ if not data then -- new occupant
+ data = {affiliation='none', role='participant', jid=from, sessions={[from]=get_filtered_presence(stanza)}};
+ end
+ self._participants[to] = data;
+ self._jid_nick[from] = to;
+ room_send_occupant_list(self, from);
+ pr.attr.from = to;
+ room_broadcast_presence(self, pr);
+ room_send_history(self, from);
+ end
+ end
+ elseif type ~= 'result' then -- bad type
+ origin.send(st.error_reply(stanza, "modify", "bad-request")); -- FIXME correct error?
+ end
+ elseif not current_nick and type ~= "error" then -- not in room
+ origin.send(st.error_reply(stanza, "cancel", "not-acceptable"));
+ elseif stanza.name == "message" and type == "groupchat" then -- groupchat messages not allowed in PM
+ origin.send(st.error_reply(stanza, "modify", "bad-request"));
+ elseif stanza.name == "message" and type == "error" and get_kickable_error(stanza) then
+ log("debug", "%s kicked from %s for sending an error message", current_nick, room);
+ room_handle_to_occupant(self, origin, st.presence({type='unavailable', from=from, to=to}):tag('status'):text('This participant is kicked from the room because he sent an error message to another occupant')); -- send unavailable
+ else -- private stanza
+ local o_data = self._participants[to];
+ if o_data then
+ log("debug", "%s sent private stanza to %s (%s)", from, to, o_data.jid);
+ local jid = o_data.jid;
+ if stanza.name=='iq' and type=='get' and stanza.tags[1].attr.xmlns == 'vcard-temp' then jid = jid_bare(jid); end
+ stanza.attr.to, stanza.attr.from = jid, current_nick;
+ self:route_stanza(stanza);
+ elseif type ~= "error" and type ~= "result" then -- recipient not in room
+ origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room"));
+ end
+ end
+end
+
+local function room_handle_to_room(self, origin, stanza) -- presence changes and groupchat messages, along with disco/etc
+ local type = stanza.attr.type;
+ if stanza.name == "iq" and type == "get" then -- disco requests
+ local xmlns = stanza.tags[1].attr.xmlns;
+ if xmlns == "http://jabber.org/protocol/disco#info" then
+ origin.send(room_get_disco_info(self, stanza));
+ elseif xmlns == "http://jabber.org/protocol/disco#items" then
+ origin.send(room_get_disco_items(self, stanza));
+ else
+ origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
+ end
+ elseif stanza.name == "message" and type == "groupchat" then
+ 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
+ origin.send(st.error_reply(stanza, "cancel", "not-acceptable"));
+ 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
+ else
+ room_broadcast_message(self, stanza, true);
+ end
+ end
+ elseif stanza.name == "presence" then -- hack - some buggy clients send presence updates to the room rather than their nick
+ local to = stanza.attr.to;
+ local current_nick = self._jid_nick[stanza.attr.from];
+ if current_nick then
+ stanza.attr.to = current_nick;
+ room_handle_to_occupant(origin, stanza);
+ stanza.attr.to = to;
+ elseif type ~= "error" and type ~= "result" then
+ origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
+ end
+ elseif stanza.name == "message" and not stanza.attr.type and #stanza.tags == 1 and self._jid_nick[stanza.attr.from]
+ and stanza.tags[1].name == "x" and stanza.tags[1].attr.xmlns == "http://jabber.org/protocol/muc#user" and #stanza.tags[1].tags == 1
+ and stanza.tags[1].tags[1].name == "invite" and stanza.tags[1].tags[1].attr.to then
+ local _from, _to = stanza.attr.from, stanza.attr.to;
+ local _invitee = stanza.tags[1].tags[1].attr.to;
+ stanza.attr.from, stanza.attr.to = _to, _invitee;
+ stanza.tags[1].tags[1].attr.from, stanza.tags[1].tags[1].attr.to = _from, nil;
+ self:route_stanza(stanza);
+ stanza.tags[1].tags[1].attr.from, stanza.tags[1].tags[1].attr.to = nil, _invitee;
+ stanza.attr.from, stanza.attr.to = _from, _to;
+ else
+ if type == "error" or type == "result" then return; end
+ origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
+ end
+end
+
+local function room_handle_stanza(self, origin, stanza)
+ local to_node, to_host, to_resource = jid_split(stanza.attr.to);
+ if to_resource and not to_node then
+ if type == "error" or type == "result" then return; end
+ origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); -- host/resource
+ elseif to_resource then
+ room_handle_to_occupant(self, origin, stanza);
+ elseif to_node then
+ room_handle_to_room(self, origin, stanza)
+ else -- to the main muc domain
+ end
+end
+
+module "muc"
+
+function new_room(jid)
+ return {
+ jid = jid;
+ handle_stanza = room_handle_stanza;
+ set_subject = room_set_subject;
+ route_stanza = function(room, stanza) end; -- Replace with a routing function, e.g., function(room, stanza) core_route_stanza(origin, stanza); end
+ _jid_nick = {};
+ _participants = {};
+ _data = {};
+ }
+end
+
+return _M;
+
+--[[function get_disco_info(stanza)
+ return st.iq({type='result', id=stanza.attr.id, from=muc_domain, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#info")
+ :tag("identity", {category='conference', type='text', name=muc_name}):up()
+ :tag("feature", {var="http://jabber.org/protocol/muc"}); -- TODO cache disco reply
+end
+function get_disco_items(stanza)
+ local reply = st.iq({type='result', id=stanza.attr.id, from=muc_domain, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#items");
+ for room in pairs(rooms_info:get()) do
+ reply:tag("item", {jid=room, name=rooms_info:get(room, "name")}):up();
+ end
+ return reply; -- TODO cache disco reply
+end]]
+
+--[[function handle_to_domain(origin, stanza)
+ 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));
+ elseif xmlns == "http://jabber.org/protocol/disco#items" then
+ origin.send(get_disco_items(stanza));
+ else
+ origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); -- TODO disco/etc
+ end
+ else
+ origin.send(st.error_reply(stanza, "cancel", "service-unavailable", "The muc server doesn't deal with messages and presence directed at it"));
+ end
+end
+
+register_component(muc_domain, function(origin, stanza)
+ local to_node, to_host, to_resource = jid_split(stanza.attr.to);
+ if to_resource and not to_node then
+ if type == "error" or type == "result" then return; end
+ origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); -- host/resource
+ elseif to_resource then
+ handle_to_occupant(origin, stanza);
+ elseif to_node then
+ handle_to_room(origin, stanza)
+ else -- to the main muc domain
+ if type == "error" or type == "result" then return; end
+ handle_to_domain(origin, stanza);
+ end
+end);]]
+
+--[[module.unload = function()
+ deregister_component(muc_domain);
+end
+module.save = function()
+ return {rooms = rooms.data; jid_nick = jid_nick.data; rooms_info = rooms_info.data; persist_list = persist_list};
+end
+module.restore = function(data)
+ rooms.data, jid_nick.data, rooms_info.data, persist_list =
+ data.rooms or {}, data.jid_nick or {}, data.rooms_info or {}, data.persist_list or {};
+end]]
diff --git a/util/sasl.lua b/util/sasl.lua
index 2740b427..54715613 100644
--- a/util/sasl.lua
+++ b/util/sasl.lua
@@ -26,8 +26,6 @@ local math = require "math"
local type = type
local error = error
local print = print
-local idna_ascii = require "util.encodings".idna.to_ascii
-local idna_unicode = require "util.encodings".idna.to_unicode
module "sasl"
@@ -62,8 +60,10 @@ local function new_plain(realm, password_handler)
return object
end
+
+-- implementing RFC 2831
local function new_digest_md5(realm, password_handler)
- --TODO maybe support for authzid
+ --TODO complete support for authzid
local function serialize(message)
local data = ""
@@ -131,29 +131,29 @@ local function new_digest_md5(realm, password_handler)
local function parse(data)
message = {}
for k, v in gmatch(data, [[([%w%-]+)="?([^",]*)"?,?]]) do -- FIXME The hacky regex makes me shudder
- message[k] = v
+ message[k] = v;
end
- return message
+ return message;
end
- local object = { mechanism = "DIGEST-MD5", realm = realm, password_handler = password_handler}
+ local object = { mechanism = "DIGEST-MD5", realm = realm, password_handler = password_handler};
--TODO: something better than math.random would be nice, maybe OpenSSL's random number generator
- object.nonce = generate_uuid()
- object.step = 0
- object.nonce_count = {}
+ object.nonce = generate_uuid();
+ object.step = 0;
+ object.nonce_count = {};
function object.feed(self, message)
- self.step = self.step + 1
+ self.step = self.step + 1;
if (self.step == 1) then
local challenge = serialize({ nonce = object.nonce,
qop = "auth",
charset = "utf-8",
algorithm = "md5-sess",
realm = self.realm});
- return "challenge", challenge
+ return "challenge", challenge;
elseif (self.step == 2) then
- local response = parse(message)
+ local response = parse(message);
-- check for replay attack
if response["nc"] then
if self.nonce_count[response["nc"]] then return "failure", "not-authorized" end
@@ -161,13 +161,13 @@ local function new_digest_md5(realm, password_handler)
-- check for username, it's REQUIRED by RFC 2831
if not response["username"] then
- return "failure", "malformed-request"
+ return "failure", "malformed-request";
end
- self["username"] = response["username"]
+ self["username"] = response["username"];
-- check for nonce, ...
if not response["nonce"] then
- return "failure", "malformed-request"
+ return "failure", "malformed-request";
else
-- check if it's the right nonce
if response["nonce"] ~= tostring(self.nonce) then return "failure", "malformed-request" end
@@ -186,44 +186,53 @@ local function new_digest_md5(realm, password_handler)
if response["charset"] == nil then
decoder = utf8tolatin1ifpossible;
elseif response["charset"] ~= "utf-8" then
- return "failure", "incorrect-encoding", "The client's response uses "..response["charset"].." for encoding with isn't supported by sasl.lua. Supported encodings are latin or utf-8."
+ return "failure", "incorrect-encoding", "The client's response uses "..response["charset"].." for encoding with isn't supported by sasl.lua. Supported encodings are latin or utf-8.";
end
- local domain = ""
- local protocol = ""
+ local domain = "";
+ local protocol = "";
if response["digest-uri"] then
- protocol, domain = response["digest-uri"]:match("(%w+)/(.*)$")
+ protocol, domain = response["digest-uri"]:match("(%w+)/(.*)$");
if protocol == nil or domain == nil then return "failure", "malformed-request" end
else
return "failure", "malformed-request", "Missing entry for digest-uri in SASL message."
end
--TODO maybe realm support
- self.username = response["username"]
+ self.username = response["username"];
local password_encoding, Y = self.password_handler(response["username"], response["realm"], "DIGEST-MD5", decoder)
if Y == nil then return "failure", "not-authorized"
elseif Y == false then return "failure", "account-disabled" end
-
- local A1 = Y..":"..response["nonce"]..":"..response["cnonce"]--:authzid
+ local A1 = "";
+ if response.authzid then
+ if response.authzid == self.username.."@"..self.realm then
+ log("warn", "Client is violating XMPP RFC. See section 6.1 of RFC 3920.");
+ A1 = Y..":"..response["nonce"]..":"..response["cnonce"]..":"..response.authzid;
+ else
+ A1 = "?";
+ end
+ else
+ A1 = Y..":"..response["nonce"]..":"..response["cnonce"];
+ end
local A2 = "AUTHENTICATE:"..protocol.."/"..domain;
- local HA1 = md5(A1, true)
- local HA2 = md5(A2, true)
+ local HA1 = md5(A1, true);
+ local HA2 = md5(A2, true);
- local KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2
- local response_value = md5(KD, true)
+ local KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2;
+ local response_value = md5(KD, true);
if response_value == response["response"] then
-- calculate rspauth
A2 = ":"..protocol.."/"..domain;
- HA1 = md5(A1, true)
- HA2 = md5(A2, true)
+ HA1 = md5(A1, true);
+ HA2 = md5(A2, true);
KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2
- local rspauth = md5(KD, true)
- self.authenticated = true
- return "challenge", serialize({rspauth = rspauth})
+ local rspauth = md5(KD, true);
+ self.authenticated = true;
+ return "challenge", serialize({rspauth = rspauth});
else
return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated."
end
@@ -232,7 +241,7 @@ local function new_digest_md5(realm, password_handler)
else return "failure", "malformed-request" end
end
end
- return object
+ return object;
end
local function new_anonymous(realm, password_handler)
diff --git a/util/serialization.lua b/util/serialization.lua
index 4da811ae..1ffd3e16 100644
--- a/util/serialization.lua
+++ b/util/serialization.lua
@@ -53,7 +53,7 @@ local function _simplesave(o, ind, t, func)
func(t, (o and "true" or "false"));
else
log("error", "cannot serialize a %s: %s", type(o), debug_traceback())
- func(t, "nil,\n");
+ func(t, "nil");
end
end
diff --git a/util/stanza.lua b/util/stanza.lua
index 7e40dfa4..526bb2f0 100644
--- a/util/stanza.lua
+++ b/util/stanza.lua
@@ -258,10 +258,9 @@ function reply(orig)
return stanza(orig.name, orig.attr and { to = orig.attr.from, from = orig.attr.to, id = orig.attr.id, type = ((orig.name == "iq" and "result") or orig.attr.type) });
end
-function error_reply(orig, type, condition, message, clone)
+function error_reply(orig, type, condition, message)
local t = reply(orig);
t.attr.type = "error";
- -- TODO use clone
t:tag("error", {type = type})
:tag(condition, {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"}):up();
if (message) then t:tag("text"):text(message):up(); end