aboutsummaryrefslogtreecommitdiffstats
path: root/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'plugins')
-rw-r--r--plugins/mod_admin_adhoc.lua2
-rw-r--r--plugins/mod_admin_telnet.lua141
-rw-r--r--plugins/mod_auth_anonymous.lua13
-rw-r--r--plugins/mod_auth_internal_hashed.lua33
-rw-r--r--plugins/mod_auth_internal_plain.lua10
-rw-r--r--plugins/mod_bosh.lua177
-rw-r--r--plugins/mod_c2s.lua7
-rw-r--r--plugins/mod_component.lua335
-rw-r--r--plugins/mod_dialback.lua35
-rw-r--r--plugins/mod_http.lua113
-rw-r--r--plugins/mod_http_errors.lua76
-rw-r--r--plugins/mod_http_files.lua57
-rw-r--r--plugins/mod_httpserver.lua97
-rw-r--r--plugins/mod_iq.lua8
-rw-r--r--plugins/mod_message.lua1
-rw-r--r--plugins/mod_motd.lua21
-rw-r--r--plugins/mod_posix.lua2
-rw-r--r--plugins/mod_proxy65.lua187
-rw-r--r--plugins/mod_pubsub.lua1
-rw-r--r--plugins/mod_s2s/mod_s2s.lua (renamed from plugins/s2s/mod_s2s.lua)59
-rw-r--r--plugins/mod_s2s/s2sout.lib.lua (renamed from plugins/s2s/s2sout.lib.lua)14
-rw-r--r--plugins/mod_saslauth.lua13
-rw-r--r--plugins/mod_version.lua2
-rw-r--r--plugins/muc/mod_muc.lua8
-rw-r--r--plugins/muc/muc.lib.lua45
25 files changed, 967 insertions, 490 deletions
diff --git a/plugins/mod_admin_adhoc.lua b/plugins/mod_admin_adhoc.lua
index 6f1357a9..4d2c60d7 100644
--- a/plugins/mod_admin_adhoc.lua
+++ b/plugins/mod_admin_adhoc.lua
@@ -24,7 +24,7 @@ local dataforms_new = require "util.dataforms".new;
local array = require "util.array";
local modulemanager = require "modulemanager";
-module:depends"adhoc";
+module:depends("adhoc");
local adhoc_new = module:require "adhoc".new;
function add_user_command_handler(self, data, state)
diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua
index 3799efc0..f4fdb39e 100644
--- a/plugins/mod_admin_telnet.lua
+++ b/plugins/mod_admin_telnet.lua
@@ -17,7 +17,8 @@ local console_listener = { default_port = 5582; default_mode = "*l"; interface =
local iterators = require "util.iterators";
local keys, values = iterators.keys, iterators.values;
-local jid_bare = require "util.jid".bare;
+local jid = require "util.jid";
+local jid_bare, jid_split = jid.bare, jid.split;
local set, array = require "util.set", require "util.array";
local cert_verify_identity = require "util.x509".verify_identity;
@@ -277,8 +278,12 @@ local function get_hosts_set(hosts, module)
return set.new { hosts };
elseif hosts == nil then
local mm = require "modulemanager";
- return set.new(array.collect(keys(prosody.hosts)))
+ local hosts_set = set.new(array.collect(keys(prosody.hosts)))
/ function (host) return prosody.hosts[host].type == "local" or module and mm.is_loaded(host, module); end;
+ if module and mm.get_module("*", module) then
+ hosts_set:add("*");
+ end
+ return hosts_set;
end
end
@@ -288,16 +293,22 @@ function def_env.module:load(name, hosts, config)
hosts = get_hosts_set(hosts);
-- Load the module for each host
- local ok, err, count = true, nil, 0;
+ local ok, err, count, mod = true, nil, 0, nil;
for host in hosts do
if (not mm.is_loaded(host, name)) then
- ok, err = mm.load(host, name, config);
- if not ok then
+ mod, err = mm.load(host, name, config);
+ if not mod then
ok = false;
+ if err == "global-module-already-loaded" then
+ if count > 0 then
+ ok, err, count = true, nil, 1;
+ end
+ break;
+ end
self.session.print(err or "Unknown error loading module");
else
count = count + 1;
- self.session.print("Loaded for "..host);
+ self.session.print("Loaded for "..mod.module.host);
end
end
end
@@ -330,11 +341,15 @@ end
function def_env.module:reload(name, hosts)
local mm = require "modulemanager";
- hosts = get_hosts_set(hosts, name);
-
+ hosts = array.collect(get_hosts_set(hosts, name)):sort(function (a, b)
+ if a == "*" then return true
+ elseif b == "*" then return false
+ else return a < b; end
+ end);
+
-- Reload the module for each host
local ok, err, count = true, nil, 0;
- for host in hosts do
+ for _, host in ipairs(hosts) do
if mm.is_loaded(host, name) then
ok, err = mm.reload(host, name);
if not ok then
@@ -355,6 +370,7 @@ end
function def_env.module:list(hosts)
if hosts == nil then
hosts = array.collect(keys(prosody.hosts));
+ table.insert(hosts, 1, "*");
end
if type(hosts) == "string" then
hosts = { hosts };
@@ -365,8 +381,8 @@ function def_env.module:list(hosts)
local print = self.session.print;
for _, host in ipairs(hosts) do
- print(host..":");
- local modules = array.collect(keys(prosody.hosts[host] and prosody.hosts[host].modules or {})):sort();
+ print((host == "*" and "Global" or host)..":");
+ local modules = array.collect(keys(modulemanager.get_modules(host) or {})):sort();
if #modules == 0 then
if prosody.hosts[host] then
print(" No modules loaded");
@@ -425,6 +441,16 @@ local function show_c2s(callback)
end
end
+function def_env.c2s:count(match_jid)
+ local count = 0;
+ show_c2s(function (jid, session)
+ if (not match_jid) or jid:match(match_jid) then
+ count = count + 1;
+ end
+ end);
+ return true, "Total: "..count.." clients";
+end
+
function def_env.c2s:show(match_jid)
local print, count = self.session.print, 0;
local curr_host;
@@ -483,6 +509,24 @@ function def_env.c2s:close(match_jid)
return true, "Total: "..count.." sessions closed";
end
+local function session_flags(session, line)
+ if session.cert_identity_status == "valid" then
+ line[#line+1] = "(secure)";
+ elseif session.secure then
+ line[#line+1] = "(encrypted)";
+ end
+ if session.compressed then
+ line[#line+1] = "(compressed)";
+ end
+ if session.smacks then
+ line[#line+1] = "(sm)";
+ end
+ if session.conn and session.conn:ip():match(":") then
+ line[#line+1] = "(IPv6)";
+ end
+ return table.concat(line, " ");
+end
+
def_env.s2s = {};
function def_env.s2s:show(match_jid)
local _print = self.session.print;
@@ -495,7 +539,7 @@ function def_env.s2s:show(match_jid)
for remotehost, session in pairs(host_session.s2sout) do
if (not match_jid) or remotehost:match(match_jid) or host:match(match_jid) then
count_out = count_out + 1;
- print(" "..host.." -> "..remotehost..(session.cert_identity_status == "valid" and " (secure)" or "")..(session.secure and " (encrypted)" or "")..(session.compressed and " (compressed)" or ""));
+ print(session_flags(session, {" ", host, "->", remotehost}));
if session.sendq then
print(" There are "..#session.sendq.." queued outgoing stanzas for this connection");
end
@@ -532,7 +576,7 @@ function def_env.s2s:show(match_jid)
-- Pft! is what I say to list comprehensions
or (session.hosts and #array.collect(keys(session.hosts)):filter(subhost_filter)>0)) then
count_in = count_in + 1;
- print(" "..host.." <- "..(session.from_host or "(unknown)")..(session.cert_identity_status == "valid" and " (secure)" or "")..(session.secure and " (encrypted)" or "")..(session.compressed and " (compressed)" or ""));
+ print(session_flags(session, {" ", host, "<-", session.from_host or "(unknown)"}));
if session.type == "s2sin_unauthed" then
print(" Connection not yet authenticated");
end
@@ -744,6 +788,74 @@ function def_env.host:list()
return true, i.." hosts";
end
+def_env.port = {};
+
+function def_env.port:list()
+ local print = self.session.print;
+ local services = portmanager.get_active_services().data;
+ local ordered_services, n_ports = {}, 0;
+ for service, interfaces in pairs(services) do
+ table.insert(ordered_services, service);
+ end
+ table.sort(ordered_services);
+ for _, service in ipairs(ordered_services) do
+ local ports_list = {};
+ for interface, ports in pairs(services[service]) do
+ for port in pairs(ports) do
+ table.insert(ports_list, "["..interface.."]:"..port);
+ end
+ end
+ n_ports = n_ports + #ports_list;
+ print(service..": "..table.concat(ports_list, ", "));
+ end
+ return true, #ordered_services.." services listening on "..n_ports.." ports";
+end
+
+function def_env.port:close(close_port, close_interface)
+ close_port = assert(tonumber(close_port), "Invalid port number");
+ local n_closed = 0;
+ local services = portmanager.get_active_services().data;
+ for service, interfaces in pairs(services) do
+ for interface, ports in pairs(interfaces) do
+ if not close_interface or close_interface == interface then
+ if ports[close_port] then
+ self.session.print("Closing ["..interface.."]:"..close_port.."...");
+ local ok, err = portmanager.close(interface, close_port)
+ if not ok then
+ self.session.print("Failed to close "..interface.." "..port..": "..err);
+ else
+ n_closed = n_closed + 1;
+ end
+ end
+ end
+ end
+ end
+ return true, "Closed "..n_closed.." ports";
+end
+
+def_env.muc = {};
+
+local console_room_mt = {
+ __index = function (self, k) return self.room[k]; end;
+ __tostring = function (self)
+ return "MUC room <"..self.room.jid..">";
+ end;
+};
+
+function def_env.muc:room(room_jid)
+ local room_name, host = jid_split(room_jid);
+ if not hosts[host] then
+ return nil, "No such host: "..host;
+ elseif not hosts[host].modules.muc then
+ return nil, "Host '"..host.."' is not a MUC service";
+ end
+ local room_obj = hosts[host].modules.muc.rooms[room_jid];
+ if not room_obj then
+ return nil, "No such room: "..room_jid;
+ end
+ return setmetatable({ room = room_obj }, console_room_mt);
+end
+
-------------
function printbanner(session)
@@ -774,7 +886,8 @@ if option and option ~= "short" and option ~= "full" and option ~= "graphic" the
end
end
-require "core.portmanager".register_service("console", {
+module:add_item("net-provider", {
+ name = "console";
listener = console_listener;
default_port = 5582;
private = true;
diff --git a/plugins/mod_auth_anonymous.lua b/plugins/mod_auth_anonymous.lua
index 8d790508..c080177d 100644
--- a/plugins/mod_auth_anonymous.lua
+++ b/plugins/mod_auth_anonymous.lua
@@ -6,7 +6,6 @@
-- COPYING file in the source package for more information.
--
-local log = require "util.logger".init("auth_anonymous");
local new_sasl = require "util.sasl".new;
local datamanager = require "util.datamanager";
@@ -51,15 +50,17 @@ local function dm_callback(username, host, datastore, data)
end
return username, host, datastore, data;
end
-local host = hosts[module.host];
-local _saved_disallow_s2s = host.disallow_s2s;
+
+if not module:get_option_boolean("allow_anonymous_s2s", false) then
+ module:hook("route/remote", function (event)
+ return false; -- Block outgoing s2s from anonymous users
+ end, 300);
+end
+
function module.load()
- _saved_disallow_s2s = host.disallow_s2s;
- host.disallow_s2s = module:get_option("disallow_s2s") ~= false;
datamanager.add_callback(dm_callback);
end
function module.unload()
- host.disallow_s2s = _saved_disallow_s2s;
datamanager.remove_callback(dm_callback);
end
diff --git a/plugins/mod_auth_internal_hashed.lua b/plugins/mod_auth_internal_hashed.lua
index 399044ad..607ecab4 100644
--- a/plugins/mod_auth_internal_hashed.lua
+++ b/plugins/mod_auth_internal_hashed.lua
@@ -9,22 +9,11 @@
local datamanager = require "util.datamanager";
local log = require "util.logger".init("auth_internal_hashed");
-local type = type;
-local error = error;
-local ipairs = ipairs;
-local hashes = require "util.hashes";
-local jid_bare = require "util.jid".bare;
local getAuthenticationDatabaseSHA1 = require "util.sasl.scram".getAuthenticationDatabaseSHA1;
-local config = require "core.configmanager";
local usermanager = require "core.usermanager";
local generate_uuid = require "util.uuid".generate;
local new_sasl = require "util.sasl".new;
local nodeprep = require "util.encodings".stringprep.nodeprep;
-local hosts = hosts;
-
--- COMPAT w/old trunk: remove these two lines before 0.8 release
-local hmac_sha1 = require "util.hmac".sha1;
-local sha1 = require "util.hashes".sha1;
local to_hex;
do
@@ -47,8 +36,6 @@ do
end
-local prosody = _G.prosody;
-
-- Default; can be set per-user
local iteration_count = 4096;
@@ -75,16 +62,6 @@ function new_hashpass_provider(host)
return nil, "Auth failed. Stored salt and iteration count information is not complete.";
end
- -- convert hexpass to stored_key and server_key
- -- COMPAT w/old trunk: remove before 0.8 release
- if credentials.hashpass then
- local salted_password = from_hex(credentials.hashpass);
- credentials.stored_key = sha1(hmac_sha1(salted_password, "Client Key"), true);
- credentials.server_key = to_hex(hmac_sha1(salted_password, "Server Key"));
- credentials.hashpass = nil
- datamanager.store(username, host, "accounts", credentials);
- end
-
local valid, stored_key, server_key = getAuthenticationDatabaseSHA1(password, credentials.salt, credentials.iteration_count);
local stored_key_hex = to_hex(stored_key);
@@ -158,16 +135,6 @@ function new_hashpass_provider(host)
if not credentials then return; end
end
- -- convert hexpass to stored_key and server_key
- -- COMPAT w/old trunk: remove before 0.8 release
- if credentials.hashpass then
- local salted_password = from_hex(credentials.hashpass);
- credentials.stored_key = sha1(hmac_sha1(salted_password, "Client Key"), true);
- credentials.server_key = to_hex(hmac_sha1(salted_password, "Server Key"));
- credentials.hashpass = nil
- datamanager.store(username, host, "accounts", credentials);
- end
-
local stored_key, server_key, iteration_count, salt = credentials.stored_key, credentials.server_key, credentials.iteration_count, credentials.salt;
stored_key = stored_key and from_hex(stored_key);
server_key = server_key and from_hex(server_key);
diff --git a/plugins/mod_auth_internal_plain.lua b/plugins/mod_auth_internal_plain.lua
index 93b50351..89dd7f1b 100644
--- a/plugins/mod_auth_internal_plain.lua
+++ b/plugins/mod_auth_internal_plain.lua
@@ -7,19 +7,11 @@
--
local datamanager = require "util.datamanager";
-local log = require "util.logger".init("auth_internal_plain");
-local type = type;
-local error = error;
-local ipairs = ipairs;
-local hashes = require "util.hashes";
-local jid_bare = require "util.jid".bare;
-local config = require "core.configmanager";
local usermanager = require "core.usermanager";
local new_sasl = require "util.sasl".new;
local nodeprep = require "util.encodings".stringprep.nodeprep;
-local hosts = hosts;
-local prosody = _G.prosody;
+local log = module._log;
function new_default_provider(host)
local provider = { name = "internal_plain" };
diff --git a/plugins/mod_bosh.lua b/plugins/mod_bosh.lua
index 20a6fa1f..24dc3755 100644
--- a/plugins/mod_bosh.lua
+++ b/plugins/mod_bosh.lua
@@ -10,7 +10,6 @@ module:set_global(); -- Global module
local hosts = _G.hosts;
local new_xmpp_stream = require "util.xmppstream".new;
-local httpserver = require "net.httpserver";
local sm = require "core.sessionmanager";
local sm_destroy_session = sm.destroy_session;
local new_uuid = require "util.uuid".generate;
@@ -19,7 +18,6 @@ local core_process_stanza = core_process_stanza;
local st = require "util.stanza";
local logger = require "util.logger";
local log = logger.init("mod_bosh");
-local timer = require "util.timer";
local xmlns_streams = "http://etherx.jabber.org/streams";
local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
@@ -79,11 +77,12 @@ local inactive_sessions = {}; -- Sessions which have no open requests
-- Used to respond to idle sessions (those with waiting requests)
local waiting_requests = {};
function on_destroy_request(request)
+ log("debug", "Request destroyed: %s", tostring(request));
waiting_requests[request] = nil;
- local session = sessions[request.sid];
+ local session = sessions[request.context.sid];
if session then
local requests = session.requests;
- for i,r in ipairs(requests) do
+ for i, r in ipairs(requests) do
if r == request then
t_remove(requests, i);
break;
@@ -99,27 +98,30 @@ function on_destroy_request(request)
end
end
-function handle_request(method, body, request)
- if (not body) or request.method ~= "POST" then
- if request.method == "OPTIONS" then
- local headers = {};
- for k,v in pairs(default_headers) do headers[k] = v; end
- headers["Content-Type"] = nil;
- return { headers = headers, body = "" };
- else
- return "<html><body>You really don't look like a BOSH client to me... what do you want?</body></html>";
- end
- end
- if not method then
- log("debug", "Request %s suffered error %s", tostring(request.id), body);
- return;
- end
- --log("debug", "Handling new request %s: %s\n----------", request.id, tostring(body));
- request.notopen = true;
- request.log = log;
- request.on_destroy = on_destroy_request;
-
- local stream = new_xmpp_stream(request, stream_callbacks);
+local function handle_GET(request)
+ return [[<html><body>
+ <p>It works! Now point your BOSH client to this URL to connect to Prosody.</p>
+ <p>For more information see <a href="http://prosody.im/doc/setting_up_bosh">Prosody: Setting up BOSH</a>.</p>
+</body></html>]];
+end
+
+function handle_OPTIONS(request)
+ local headers = {};
+ for k,v in pairs(default_headers) do headers[k] = v; end
+ headers["Content-Type"] = nil;
+ return { headers = headers, body = "" };
+end
+
+function handle_POST(event)
+ log("debug", "Handling new request %s: %s\n----------", tostring(event.request), tostring(event.request.body));
+
+ local request, response = event.request, event.response;
+ response.on_destroy = on_destroy_request;
+ local body = request.body;
+
+ local context = { request = request, response = response, notopen = true };
+ local stream = new_xmpp_stream(context, stream_callbacks);
+ response.context = context;
-- stream:feed() calls the stream_callbacks, so all stanzas in
-- the body are processed in this next line before it returns.
@@ -131,7 +133,7 @@ function handle_request(method, body, request)
-- Stanzas (if any) in the request have now been processed, and
-- we take care of the high-level BOSH logic here, including
-- giving a response or putting the request "on hold".
- local session = sessions[request.sid];
+ local session = sessions[context.sid];
if session then
-- Session was marked as inactive, since we have
-- a request open now, unmark it
@@ -140,8 +142,11 @@ function handle_request(method, body, request)
end
local r = session.requests;
- log("debug", "Session %s has %d out of %d requests open", request.sid, #r, session.bosh_hold);
- log("debug", "and there are %d things in the send_buffer", #session.send_buffer);
+ log("debug", "Session %s has %d out of %d requests open", context.sid, #r, session.bosh_hold);
+ log("debug", "and there are %d things in the send_buffer:", #session.send_buffer);
+ for i, thing in ipairs(session.send_buffer) do
+ log("debug", " %s", tostring(thing));
+ end
if #r > session.bosh_hold then
-- We are holding too many requests, send what's in the buffer,
log("debug", "We are holding too many requests, so...");
@@ -161,12 +166,11 @@ function handle_request(method, body, request)
session.send(resp);
end
- if not request.destroyed then
+ if not response.finished then
-- We're keeping this request open, to respond later
log("debug", "Have nothing to say, so leaving request unanswered for now");
if session.bosh_wait then
- request.reply_before = os_time() + session.bosh_wait;
- waiting_requests[request] = true;
+ waiting_requests[response] = os_time() + session.bosh_wait;
end
end
@@ -175,7 +179,7 @@ function handle_request(method, body, request)
session:close();
return nil;
else
- return true; -- Inform httpserver we shall reply later
+ return true; -- Inform http server we shall reply later
end
end
end
@@ -214,11 +218,10 @@ local function bosh_close_stream(session, reason)
log("info", "Disconnecting client, <stream:error> is: %s", tostring(close_reply));
end
- local session_close_response = { headers = default_headers, body = tostring(close_reply) };
-
+ local response_body = tostring(close_reply);
for _, held_request in ipairs(session.requests) do
- held_request:send(session_close_response);
- held_request:destroy();
+ held_request.headers = default_headers;
+ held_request:send(response_body);
end
sessions[session.sid] = nil;
inactive_sessions[session] = nil;
@@ -226,12 +229,13 @@ local function bosh_close_stream(session, reason)
end
-- Handle the <body> tag in the request payload.
-function stream_callbacks.streamopened(request, attr)
+function stream_callbacks.streamopened(context, attr)
+ local request, response = context.request, context.response;
local sid = attr.sid;
log("debug", "BOSH body open (sid: %s)", sid or "<none>");
if not sid then
-- New session request
- request.notopen = nil; -- Signals that we accept this opening tag
+ context.notopen = nil; -- Signals that we accept this opening tag
-- TODO: Sanity checks here (rid, to, known host, etc.)
if not hosts[attr.to] then
@@ -239,7 +243,7 @@ function stream_callbacks.streamopened(request, attr)
log("debug", "BOSH client tried to connect to unknown host: %s", tostring(attr.to));
local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate",
["xmlns:stream"] = xmlns_streams, condition = "host-unknown" });
- request:send(tostring(close_reply));
+ response:send(tostring(close_reply));
return;
end
@@ -258,8 +262,7 @@ function stream_callbacks.streamopened(request, attr)
session.log("debug", "BOSH session created for request from %s", session.ip);
log("info", "New BOSH session, assigned it sid '%s'", sid);
- local r, send_buffer = session.requests, session.send_buffer;
- local response = { headers = default_headers }
+ local r = session.requests;
function session.send(s)
-- We need to ensure that outgoing stanzas have the jabber:client xmlns
if s.attr and not s.attr.xmlns then
@@ -270,25 +273,14 @@ function stream_callbacks.streamopened(request, attr)
local oldest_request = r[1];
if oldest_request and (not(auto_cork) or waiting_requests[oldest_request]) then
log("debug", "We have an open request, so sending on that");
- response.body = t_concat({
+ oldest_request.headers = default_headers;
+ oldest_request:send(t_concat({
"<body xmlns='http://jabber.org/protocol/httpbind' ",
session.bosh_terminate and "type='terminate' " or "",
"sid='", sid, "' xmlns:stream = 'http://etherx.jabber.org/streams'>",
tostring(s),
"</body>"
- });
- oldest_request:send(response);
- --log("debug", "Sent");
- if oldest_request.stayopen then
- if #r>1 then
- -- Move front request to back
- t_insert(r, oldest_request);
- t_remove(r, 1);
- end
- else
- log("debug", "Destroying the request now...");
- oldest_request:destroy();
- end
+ }));
elseif s ~= "" then
log("debug", "Saved to send buffer because there are %d open requests", #r);
-- Hmm, no requests are open :(
@@ -304,7 +296,7 @@ function stream_callbacks.streamopened(request, attr)
hosts[session.host].events.fire_event("stream-features", { origin = session, features = features });
fire_event("stream-features", session, features);
--xmpp:version='1.0' xmlns:xmpp='urn:xmpp:xbosh'
- local response = st.stanza("body", { xmlns = xmlns_bosh,
+ local body = st.stanza("body", { xmlns = xmlns_bosh,
wait = attr.wait,
inactivity = tostring(BOSH_DEFAULT_INACTIVITY),
polling = tostring(BOSH_DEFAULT_POLLING),
@@ -316,7 +308,8 @@ function stream_callbacks.streamopened(request, attr)
["xmlns:xmpp"] = "urn:xmpp:xbosh",
["xmlns:stream"] = "http://etherx.jabber.org/streams"
}):add_child(features);
- request:send{ headers = default_headers, body = tostring(response) };
+ response.headers = default_headers;
+ response:send(tostring(body));
request.sid = sid;
return;
@@ -326,8 +319,9 @@ function stream_callbacks.streamopened(request, attr)
if not session then
-- Unknown sid
log("info", "Client tried to use sid '%s' which we don't know about", sid);
- request:send{ headers = default_headers, body = tostring(st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", condition = "item-not-found" })) };
- request.notopen = nil;
+ response.headers = default_headers;
+ response:send(tostring(st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", condition = "item-not-found" })));
+ context.notopen = nil;
return;
end
@@ -339,10 +333,10 @@ function stream_callbacks.streamopened(request, attr)
elseif diff <= 0 then
-- Repeated, ignore
session.log("debug", "rid repeated (on request %s), ignoring: %s (diff %d)", request.id, session.rid, diff);
- request.notopen = nil;
- request.ignore = true;
- request.sid = sid;
- t_insert(session.requests, request);
+ context.notopen = nil;
+ context.ignore = true;
+ context.sid = sid;
+ t_insert(session.requests, response);
return;
end
session.rid = rid;
@@ -354,9 +348,9 @@ function stream_callbacks.streamopened(request, attr)
session.bosh_terminate = true;
end
- request.notopen = nil; -- Signals that we accept this opening tag
- t_insert(session.requests, request);
- request.sid = sid;
+ context.notopen = nil; -- Signals that we accept this opening tag
+ t_insert(session.requests, response);
+ context.sid = sid;
if session.notopen then
local features = st.stanza("stream:features");
@@ -367,10 +361,10 @@ function stream_callbacks.streamopened(request, attr)
end
end
-function stream_callbacks.handlestanza(request, stanza)
- if request.ignore then return; end
+function stream_callbacks.handlestanza(context, stanza)
+ if context.ignore then return; end
log("debug", "BOSH stanza received: %s\n", stanza:top_tag());
- local session = sessions[request.sid];
+ local session = sessions[context.sid];
if session then
if stanza.attr.xmlns == xmlns_bosh then
stanza.attr.xmlns = nil;
@@ -379,14 +373,17 @@ function stream_callbacks.handlestanza(request, stanza)
end
end
-function stream_callbacks.error(request, error)
+function stream_callbacks.error(context, error)
log("debug", "Error parsing BOSH request payload; %s", error);
- if not request.sid then
- request:send({ headers = default_headers, status = "400 Bad Request" });
+ if not context.sid then
+ local response = context.response;
+ response.headers = default_headers;
+ response.status_code = 400;
+ response:send();
return;
end
- local session = sessions[request.sid];
+ local session = sessions[context.sid];
if error == "stream-error" then -- Remote stream error, we close normally
session:close();
else
@@ -399,13 +396,13 @@ function on_timer()
-- log("debug", "Checking for requests soon to timeout...");
-- Identify requests timing out within the next few seconds
local now = os_time() + 3;
- for request in pairs(waiting_requests) do
- if request.reply_before <= now then
- log("debug", "%s was soon to timeout, sending empty response", request.id);
+ for request, reply_before in pairs(waiting_requests) do
+ if reply_before <= now then
+ log("debug", "%s was soon to timeout (at %d, now %d), sending empty response", tostring(request), reply_before, now);
-- Send empty response to let the
-- client know we're still here
if request.conn then
- sessions[request.sid].send("");
+ sessions[request.context.sid].send("");
end
end
end
@@ -429,15 +426,19 @@ function on_timer()
end
return 1;
end
-
-
-local function setup()
- local ports = module:get_option_array("bosh_ports") or { 5280 };
- httpserver.new_from_config(ports, handle_request, { base = "http-bind" });
- timer.add_task(1, on_timer);
-end
-if prosody.start_time then -- already started
- setup();
-else
- prosody.events.add_handler("server-started", setup);
+module:add_timer(1, on_timer);
+
+function module.add_host(module)
+ module:depends("http");
+ module:provides("http", {
+ default_path = "/http-bind";
+ route = {
+ ["GET"] = handle_GET;
+ ["GET /"] = handle_GET;
+ ["OPTIONS"] = handle_OPTIONS;
+ ["OPTIONS /"] = handle_OPTIONS;
+ ["POST"] = handle_POST;
+ ["POST /"] = handle_POST;
+ };
+ });
end
diff --git a/plugins/mod_c2s.lua b/plugins/mod_c2s.lua
index 743fe3d2..55c53e2d 100644
--- a/plugins/mod_c2s.lua
+++ b/plugins/mod_c2s.lua
@@ -11,14 +11,12 @@ module:set_global();
local add_task = require "util.timer".add_task;
local new_xmpp_stream = require "util.xmppstream".new;
local nameprep = require "util.encodings".stringprep.nameprep;
-local portmanager = require "core.portmanager";
local sessionmanager = require "core.sessionmanager";
local st = require "util.stanza";
local sm_new_session, sm_destroy_session = sessionmanager.new_session, sessionmanager.destroy_session;
local uuid_generate = require "util.uuid".generate;
local xpcall, tostring, type = xpcall, tostring, type;
-local format = string.format;
local traceback = debug.traceback;
local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
@@ -55,8 +53,9 @@ function stream_callbacks.streamopened(session, attr)
return;
end
- send("<?xml version='1.0'?>");
- send(format("<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='%s' from='%s' version='1.0' xml:lang='en'>", session.streamid, session.host));
+ send("<?xml version='1.0'?>"..st.stanza("stream:stream", {
+ xmlns = 'jabber:client', ["xmlns:stream"] = 'http://etherx.jabber.org/streams';
+ id = session.streamid, from = session.host, version = '1.0', ["xml:lang"] = 'en' }):top_tag());
(session.log or log)("debug", "Sent reply <stream:stream> to client");
session.notopen = nil;
diff --git a/plugins/mod_component.lua b/plugins/mod_component.lua
index f7d09930..738124cc 100644
--- a/plugins/mod_component.lua
+++ b/plugins/mod_component.lua
@@ -6,95 +6,300 @@
-- COPYING file in the source package for more information.
--
-if module:get_host_type() ~= "component" then
- error("Don't load mod_component manually, it should be for a component, please see http://prosody.im/doc/components", 0);
-end
+module:set_global();
local t_concat = table.concat;
+local logger = require "util.logger";
local sha1 = require "util.hashes".sha1;
local st = require "util.stanza";
+local jid_split = require "util.jid".split;
+local new_xmpp_stream = require "util.xmppstream".new;
+local uuid_gen = require "util.uuid".generate;
+
+
local log = module._log;
-local main_session, send;
+local sessions = module:shared("sessions");
-local function on_destroy(session, err)
- if main_session == session then
- connected = false;
- main_session = nil;
+function module.add_host(module)
+ if module:get_host_type() ~= "component" then
+ error("Don't load mod_component manually, it should be for a component, please see http://prosody.im/doc/components", 0);
+ end
+
+ local env = module.environment;
+ env.connected = false;
+
+ local send;
+
+ local function on_destroy(session, err)
+ env.connected = false;
send = nil;
session.on_destroy = nil;
end
+
+ -- Handle authentication attempts by component
+ local function handle_component_auth(event)
+ local session, stanza = event.origin, event.stanza;
+
+ if session.type ~= "component_unauthed" then return; end
+
+ if (not session.host) or #stanza.tags > 0 then
+ (session.log or log)("warn", "Invalid component handshake for host: %s", session.host);
+ session:close("not-authorized");
+ return true;
+ end
+
+ local secret = module:get_option("component_secret");
+ if not secret then
+ (session.log or log)("warn", "Component attempted to identify as %s, but component_secret is not set", session.host);
+ session:close("not-authorized");
+ return true;
+ end
+
+ local supplied_token = t_concat(stanza);
+ local calculated_token = sha1(session.streamid..secret, true);
+ if supplied_token:lower() ~= calculated_token:lower() then
+ module:log("info", "Component authentication failed for %s", session.host);
+ session:close{ condition = "not-authorized", text = "Given token does not match calculated token" };
+ return true;
+ end
+
+ if env.connected then
+ module:log("error", "Second component attempted to connect, denying connection");
+ session:close{ condition = "conflict", text = "Component already connected" };
+ return true;
+ end
+
+ env.connected = true;
+ send = session.send;
+ session.on_destroy = on_destroy;
+ session.component_validate_from = module:get_option_boolean("validate_from_addresses", true);
+ session.type = "component";
+ module:log("info", "External component successfully authenticated");
+ session.send(st.stanza("handshake"));
+
+ return true;
+ end
+ module:hook("stanza/jabber:component:accept:handshake", handle_component_auth);
+
+ -- Handle stanzas addressed to this component
+ local function handle_stanza(event)
+ local stanza = event.stanza;
+ if send then
+ stanza.attr.xmlns = nil;
+ send(stanza);
+ else
+ module:log("warn", "Component not connected, bouncing error for: %s", stanza:top_tag());
+ if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then
+ event.origin.send(st.error_reply(stanza, "wait", "service-unavailable", "Component unavailable"));
+ end
+ end
+ return true;
+ end
+
+ module:hook("iq/bare", handle_stanza, -1);
+ module:hook("message/bare", handle_stanza, -1);
+ module:hook("presence/bare", handle_stanza, -1);
+ module:hook("iq/full", handle_stanza, -1);
+ module:hook("message/full", handle_stanza, -1);
+ module:hook("presence/full", handle_stanza, -1);
+ module:hook("iq/host", handle_stanza, -1);
+ module:hook("message/host", handle_stanza, -1);
+ module:hook("presence/host", handle_stanza, -1);
end
-local function handle_stanza(event)
- local stanza = event.stanza;
- if send then
- stanza.attr.xmlns = nil;
- send(stanza);
- else
- log("warn", "Component not connected, bouncing error for: %s", stanza:top_tag());
- if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then
- event.origin.send(st.error_reply(stanza, "wait", "service-unavailable", "Component unavailable"));
+--- Network and stream part ---
+
+local xmlns_component = 'jabber:component:accept';
+
+local listener = {};
+
+--- Callbacks/data for xmppstream to handle streams for us ---
+
+local stream_callbacks = { default_ns = xmlns_component };
+
+local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
+
+function stream_callbacks.error(session, error, data, data2)
+ if session.destroyed then return; end
+ module:log("warn", "Error processing component stream: "..tostring(error));
+ if error == "no-stream" then
+ session:close("invalid-namespace");
+ elseif error == "parse-error" then
+ session.log("warn", "External component %s XML parse error: %s", tostring(session.host), tostring(data));
+ session:close("not-well-formed");
+ elseif error == "stream-error" then
+ local condition, text = "undefined-condition";
+ for child in data:children() do
+ if child.attr.xmlns == xmlns_xmpp_streams then
+ if child.name ~= "text" then
+ condition = child.name;
+ else
+ text = child:get_text();
+ end
+ if condition ~= "undefined-condition" and text then
+ break;
+ end
+ end
end
+ text = condition .. (text and (" ("..text..")") or "");
+ session.log("info", "Session closed by remote with error: %s", text);
+ session:close(nil, text);
end
- return true;
end
-module:hook("iq/bare", handle_stanza, -1);
-module:hook("message/bare", handle_stanza, -1);
-module:hook("presence/bare", handle_stanza, -1);
-module:hook("iq/full", handle_stanza, -1);
-module:hook("message/full", handle_stanza, -1);
-module:hook("presence/full", handle_stanza, -1);
-module:hook("iq/host", handle_stanza, -1);
-module:hook("message/host", handle_stanza, -1);
-module:hook("presence/host", handle_stanza, -1);
-
---- Handle authentication attempts by components
-function handle_component_auth(event)
- local session, stanza = event.origin, event.stanza;
-
- if session.type ~= "component" then return; end
- if main_session == session then return; end
+function stream_callbacks.streamopened(session, attr)
+ if not hosts[attr.to] or not hosts[attr.to].modules.component then
+ session:close{ condition = "host-unknown", text = tostring(attr.to).." does not match any configured external components" };
+ return;
+ end
+ session.host = attr.to;
+ session.streamid = uuid_gen();
+ session.notopen = nil;
+ -- Return stream header
+ session.send("<?xml version='1.0'?>");
+ session.send(st.stanza("stream:stream", { xmlns=xmlns_component,
+ ["xmlns:stream"]='http://etherx.jabber.org/streams', id=session.streamid, from=session.host }):top_tag());
+end
- if (not session.host) or #stanza.tags > 0 then
- (session.log or log)("warn", "Invalid component handshake for host: %s", session.host);
- session:close("not-authorized");
- return true;
+function stream_callbacks.streamclosed(session)
+ session.log("debug", "Received </stream:stream>");
+ session:close();
+end
+
+local core_process_stanza = core_process_stanza;
+
+function stream_callbacks.handlestanza(session, stanza)
+ -- Namespaces are icky.
+ if not stanza.attr.xmlns and stanza.name == "handshake" then
+ stanza.attr.xmlns = xmlns_component;
end
-
- local secret = module:get_option("component_secret");
- if not secret then
- (session.log or log)("warn", "Component attempted to identify as %s, but component_secret is not set", session.host);
- session:close("not-authorized");
- return true;
+ if not stanza.attr.xmlns or stanza.attr.xmlns == "jabber:client" then
+ local from = stanza.attr.from;
+ if from then
+ if session.component_validate_from then
+ local _, domain = jid_split(stanza.attr.from);
+ if domain ~= session.host then
+ -- Return error
+ session.log("warn", "Component sent stanza with missing or invalid 'from' address");
+ session:close{
+ condition = "invalid-from";
+ text = "Component tried to send from address <"..tostring(from)
+ .."> which is not in domain <"..tostring(session.host)..">";
+ };
+ return;
+ end
+ end
+ else
+ stanza.attr.from = session.host; -- COMPAT: Strictly we shouldn't allow this
+ end
+ if not stanza.attr.to then
+ session.log("warn", "Rejecting stanza with no 'to' address");
+ session.send(st.error_reply(stanza, "modify", "bad-request", "Components MUST specify a 'to' address on stanzas"));
+ return;
+ end
end
-
- local supplied_token = t_concat(stanza);
- local calculated_token = sha1(session.streamid..secret, true);
- if supplied_token:lower() ~= calculated_token:lower() then
- log("info", "Component authentication failed for %s", session.host);
- session:close{ condition = "not-authorized", text = "Given token does not match calculated token" };
- return true;
+ return core_process_stanza(session, stanza);
+end
+
+--- Closing a component connection
+local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
+local default_stream_attr = { ["xmlns:stream"] = "http://etherx.jabber.org/streams", xmlns = stream_callbacks.default_ns, version = "1.0", id = "" };
+local function session_close(session, reason)
+ if session.destroyed then return; end
+ if session.conn then
+ if session.notopen then
+ session.send("<?xml version='1.0'?>");
+ session.send(st.stanza("stream:stream", default_stream_attr):top_tag());
+ end
+ if reason then
+ if type(reason) == "string" then -- assume stream error
+ module:log("info", "Disconnecting component, <stream:error> is: %s", reason);
+ session.send(st.stanza("stream:error"):tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' }));
+ elseif type(reason) == "table" then
+ if reason.condition then
+ local stanza = st.stanza("stream:error"):tag(reason.condition, stream_xmlns_attr):up();
+ if reason.text then
+ stanza:tag("text", stream_xmlns_attr):text(reason.text):up();
+ end
+ if reason.extra then
+ stanza:add_child(reason.extra);
+ end
+ module:log("info", "Disconnecting component, <stream:error> is: %s", tostring(stanza));
+ session.send(stanza);
+ elseif reason.name then -- a stanza
+ module:log("info", "Disconnecting component, <stream:error> is: %s", tostring(reason));
+ session.send(reason);
+ end
+ end
+ end
+ session.send("</stream:stream>");
+ session.conn:close();
+ listener.ondisconnect(session.conn, "stream error");
end
+end
+
+--- Component connlistener
+
+function listener.onconnect(conn)
+ local _send = conn.write;
+ local session = { type = "component_unauthed", conn = conn, send = function (data) return _send(conn, tostring(data)); end };
+
+ -- Logging functions --
+ local conn_name = "jcp"..tostring(conn):match("[a-f0-9]+$");
+ session.log = logger.init(conn_name);
+ session.close = session_close;
- -- If component not already created for this host, create one now
- if not main_session then
- connected = true;
- send = session.send;
- main_session = session;
- session.on_destroy = on_destroy;
- session.component_validate_from = module:get_option_boolean("validate_from_addresses", true);
- log("info", "Component successfully authenticated: %s", session.host);
- session.send(st.stanza("handshake"));
- else -- TODO: Implement stanza distribution
- log("error", "Multiple components bound to the same address, first one wins: %s", session.host);
- session:close{ condition = "conflict", text = "Component already connected" };
+ session.log("info", "Incoming Jabber component connection");
+
+ local stream = new_xmpp_stream(session, stream_callbacks);
+ session.stream = stream;
+
+ session.notopen = true;
+
+ function session.reset_stream()
+ session.notopen = true;
+ session.stream:reset();
+ end
+
+ function session.data(conn, data)
+ local ok, err = stream:feed(data);
+ if ok then return; end
+ module:log("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "):gsub("[%z\1-\31]", "_"));
+ session:close("not-well-formed");
end
- return true;
+ session.dispatch_stanza = stream_callbacks.handlestanza;
+
+ sessions[conn] = session;
+end
+function listener.onincoming(conn, data)
+ local session = sessions[conn];
+ session.data(conn, data);
+end
+function listener.ondisconnect(conn, err)
+ local session = sessions[conn];
+ if session then
+ (session.log or log)("info", "component disconnected: %s (%s)", tostring(session.host), tostring(err));
+ if session.on_destroy then session:on_destroy(err); end
+ sessions[conn] = nil;
+ for k in pairs(session) do
+ if k ~= "log" and k ~= "close" then
+ session[k] = nil;
+ end
+ end
+ session.destroyed = true;
+ session = nil;
+ end
end
-module:hook("stanza/jabber:component:accept:handshake", handle_component_auth);
+module:add_item("net-provider", {
+ name = "component";
+ listener = listener;
+ default_port = 5347;
+ multiplex = {
+ pattern = "^<.*:stream.*%sxmlns%s*=%s*(['\"])jabber:component%1.*>";
+ };
+});
diff --git a/plugins/mod_dialback.lua b/plugins/mod_dialback.lua
index e578c412..2299c0dc 100644
--- a/plugins/mod_dialback.lua
+++ b/plugins/mod_dialback.lua
@@ -6,8 +6,6 @@
-- COPYING file in the source package for more information.
--
-local format = string.format;
-
local hosts = _G.hosts;
local s2s_make_authenticated = require "core.s2smanager".make_authenticated;
@@ -15,9 +13,9 @@ local log = module._log;
local st = require "util.stanza";
local sha256_hash = require "util.hashes".sha256;
+local nameprep = require "util.encodings".stringprep.nameprep;
local xmlns_stream = "http://etherx.jabber.org/streams";
-local xmlns_dialback = "jabber:server:dialback";
local dialback_requests = setmetatable({}, { __mode = 'v' });
@@ -28,7 +26,7 @@ end
function initiate_dialback(session)
-- generate dialback key
session.dialback_key = generate_dialback(session.streamid, session.to_host, session.from_host);
- session.sends2s(format("<db:result from='%s' to='%s'>%s</db:result>", session.from_host, session.to_host, session.dialback_key));
+ session.sends2s(st.stanza("db:result", { from = session.from_host, to = session.to_host }):text(session.dialback_key));
session.log("info", "sent dialback key on outgoing s2s stream");
end
@@ -65,28 +63,35 @@ module:hook("stanza/jabber:server:dialback:result", function(event)
-- he wants to be identified through dialback
-- We need to check the key with the Authoritative server
local attr = stanza.attr;
- origin.hosts[attr.from] = { dialback_key = stanza[1] };
+ local to, from = nameprep(attr.to), nameprep(attr.from);
- if not hosts[attr.to] then
+ if not hosts[to] then
-- Not a host that we serve
- origin.log("info", "%s tried to connect to %s, which we don't serve", attr.from, attr.to);
+ origin.log("info", "%s tried to connect to %s, which we don't serve", from, to);
origin:close("host-unknown");
return true;
+ elseif not from then
+ origin:close("improper-addressing");
end
- dialback_requests[attr.from.."/"..origin.streamid] = origin;
+ origin.hosts[from] = { dialback_key = stanza[1] };
+
+ dialback_requests[from.."/"..origin.streamid] = origin;
+ -- COMPAT: ejabberd, gmail and perhaps others do not always set 'to' and 'from'
+ -- on streams. We fill in the session's to/from here instead.
if not origin.from_host then
- -- Just used for friendlier logging
- origin.from_host = attr.from;
+ origin.from_host = from;
end
if not origin.to_host then
- -- Just used for friendlier logging
- origin.to_host = attr.to;
+ origin.to_host = nameprep(attr.to);
end
-
- origin.log("debug", "asking %s if key %s belongs to them", attr.from, stanza[1]);
- origin.send(st.stanza("db:verify", { from = attr.to, to = attr.from, id = origin.streamid }):text(stanza[1]));
+
+ origin.log("debug", "asking %s if key %s belongs to them", from, stanza[1]);
+ module:fire_event("route/remote", {
+ from_host = to, to_host = from;
+ stanza = st.stanza("db:verify", { from = to, to = from, id = origin.streamid }):text(stanza[1]);
+ });
return true;
end
end);
diff --git a/plugins/mod_http.lua b/plugins/mod_http.lua
new file mode 100644
index 00000000..b7376831
--- /dev/null
+++ b/plugins/mod_http.lua
@@ -0,0 +1,113 @@
+-- Prosody IM
+-- Copyright (C) 2008-2012 Matthew Wild
+-- Copyright (C) 2008-2012 Waqas Hussain
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+module:set_global();
+module:depends("http_errors");
+
+local server = require "net.http.server";
+
+server.set_default_host(module:get_option_string("http_default_host"));
+
+local function normalize_path(path)
+ if path:sub(1,1) ~= "/" then path = "/"..path; end
+ if path:sub(-1,-1) == "/" then path = path:sub(1, -2); end
+ return path;
+end
+
+local function get_http_event(host, app_path, key)
+ local method, path = key:match("^(%S+)%s+(.+)$");
+ if not method then -- No path specified, default to "" (base path)
+ method, path = key, "";
+ end
+ if method:sub(1,1) == "/" then
+ return nil;
+ end
+ return method:upper().." "..host..app_path..path;
+end
+
+local function get_base_path(host_module, app_name, default_app_path)
+ return host_module:get_option("http_paths", {})[app_name] -- Host
+ or module:get_option("http_paths", {})[app_name] -- Global
+ or default_app_path; -- Default
+end
+
+function module.add_host(module)
+ local host = module.host;
+ local apps = {};
+ module.environment.apps = apps;
+ local function http_app_added(event)
+ local app_name = event.item.name;
+ local default_app_path = event.item.default_path or "/"..app_name;
+ local app_path = normalize_path(get_base_path(module, app_name, default_app_path));
+ if not app_name then
+ -- TODO: Link to docs
+ module:log("error", "HTTP app has no 'name', add one or use module:provides('http', app)");
+ return;
+ end
+ apps[app_name] = apps[app_name] or {};
+ local app_handlers = apps[app_name];
+ for key, handler in pairs(event.item.route or {}) do
+ local event_name = get_http_event(host, app_path, key);
+ if event_name then
+ if type(handler) ~= "function" then
+ local data = handler;
+ handler = function () return data; end
+ elseif event_name:sub(-2, -1) == "/*" then
+ local base_path = event_name:match("/(.+)/*$");
+ local _handler = handler;
+ handler = function (event)
+ local path = event.request.path:sub(#base_path+1);
+ return _handler(event, path);
+ end;
+ end
+ if not app_handlers[event_name] then
+ app_handlers[event_name] = handler;
+ module:hook_object_event(server, event_name, handler);
+ else
+ module:log("warn", "App %s added handler twice for '%s', ignoring", app_name, event_name);
+ end
+ else
+ module:log("error", "Invalid route in %s, %q. See http://prosody.im/doc/developers/http#routes", app_name, key);
+ end
+ end
+ end
+
+ local function http_app_removed(event)
+ local app_handlers = apps[event.item.name];
+ apps[event.item.name] = nil;
+ for event, handler in pairs(app_handlers) do
+ module:unhook_object_event(server, event, handler);
+ end
+ end
+
+ module:handle_items("http-provider", http_app_added, http_app_removed);
+
+ server.add_host(host);
+ function module.unload()
+ server.remove_host(host);
+ end
+end
+
+module:add_item("net-provider", {
+ name = "http";
+ listener = server.listener;
+ default_port = 5280;
+ multiplex = {
+ pattern = "^[A-Z]";
+ };
+});
+
+module:add_item("net-provider", {
+ name = "https";
+ listener = server.listener;
+ default_port = 5281;
+ encryption = "ssl";
+ multiplex = {
+ pattern = "^[A-Z]";
+ };
+});
diff --git a/plugins/mod_http_errors.lua b/plugins/mod_http_errors.lua
new file mode 100644
index 00000000..828216dd
--- /dev/null
+++ b/plugins/mod_http_errors.lua
@@ -0,0 +1,76 @@
+module:set_global();
+
+local server = require "net.http.server";
+local codes = require "net.http.codes";
+local termcolours = require "util.termcolours";
+
+local show_private = module:get_option_boolean("http_errors_detailed", false);
+local always_serve = module:get_option_boolean("http_errors_always_show", true);
+local default_message = { module:get_option_string("http_errors_default_message", "That's all I know.") };
+local default_messages = {
+ [400] = { "What kind of request do you call that??" };
+ [403] = { "You're not allowed to do that." };
+ [404] = { "Whatever you were looking for is not here. %";
+ "Where did you put it?", "It's behind you.", "Keep looking." };
+ [500] = { "% Check your error log for more info.";
+ "Gremlins.", "It broke.", "Don't look at me." };
+};
+
+local messages = setmetatable(module:get_option("http_errors_messages", {}), { __index = default_messages });
+
+local html = [[
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <style>
+ body{
+ margin-top:14%;
+ text-align:center;
+ background-color:#F8F8F8;
+ font-family:sans-serif;
+ }
+ h1{
+ font-size:xx-large;
+ }
+ p{
+ font-size:x-large;
+ }
+ p+p { font-size: large; font-family: courier }
+ </style>
+</head>
+<body>
+ <h1>$title</h1>
+ <p>$message</p>
+ <p>$extra</p>
+</body>
+</html>]];
+html = html:gsub("%s%s+", "");
+
+local entities = {
+ ["<"] = "&lt;", [">"] = "&gt;", ["&"] = "&amp;",
+ ["'"] = "&apos;", ["\""] = "&quot;", ["\n"] = "<br/>",
+};
+
+local function tohtml(plain)
+ return (plain:gsub("[<>&'\"\n]", entities));
+
+end
+
+local function get_page(code, extra)
+ local message = messages[code];
+ if always_serve or message then
+ message = message or default_message;
+ return (html:gsub("$(%a+)", {
+ title = rawget(codes, code) or ("Code "..tostring(code));
+ message = message[1]:gsub("%%", function ()
+ return message[math.random(2, math.max(#message,2))];
+ end);
+ extra = tohtml(extra or "");
+ }));
+ end
+end
+
+module:hook_object_event(server, "http-error", function (event)
+ return get_page(event.code, (show_private and event.private_message) or event.message);
+end);
diff --git a/plugins/mod_http_files.lua b/plugins/mod_http_files.lua
new file mode 100644
index 00000000..dc58ff5d
--- /dev/null
+++ b/plugins/mod_http_files.lua
@@ -0,0 +1,57 @@
+-- Prosody IM
+-- Copyright (C) 2008-2010 Matthew Wild
+-- Copyright (C) 2008-2010 Waqas Hussain
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+module:depends("http");
+local lfs = require "lfs";
+
+local open = io.open;
+local stat = lfs.attributes;
+
+local http_base = module:get_option_string("http_files_dir", module:get_option_string("http_path", "www_files"));
+
+-- TODO: Should we read this from /etc/mime.types if it exists? (startup time...?)
+local mime_map = {
+ html = "text/html";
+ htm = "text/html";
+ xml = "text/xml";
+ xsl = "text/xml";
+ txt = "text/plain; charset=utf-8";
+ js = "text/javascript";
+ css = "text/css";
+};
+
+function serve_file(event, path)
+ local response = event.response;
+ local full_path = http_base.."/"..path;
+ if stat(full_path, "mode") == "directory" then
+ if stat(full_path.."/index.html", "mode") == "file" then
+ return serve_file(event, path.."/index.html");
+ end
+ return 403;
+ end
+ local f, err = open(full_path, "rb");
+ if not f then
+ module:log("warn", "Failed to open file: %s", err);
+ return 404;
+ end
+ local data = f:read("*a");
+ f:close();
+ if not data then
+ return 403;
+ end
+ local ext = path:match("%.([^.]*)$");
+ response.headers.content_type = mime_map[ext]; -- Content-Type should be nil when not known
+ return response:send(data);
+end
+
+module:provides("http", {
+ route = {
+ ["GET /*"] = serve_file;
+ };
+});
+
diff --git a/plugins/mod_httpserver.lua b/plugins/mod_httpserver.lua
deleted file mode 100644
index 654aff06..00000000
--- a/plugins/mod_httpserver.lua
+++ /dev/null
@@ -1,97 +0,0 @@
--- Prosody IM
--- Copyright (C) 2008-2010 Matthew Wild
--- Copyright (C) 2008-2010 Waqas Hussain
---
--- This project is MIT/X11 licensed. Please see the
--- COPYING file in the source package for more information.
---
-
-
-local httpserver = require "net.httpserver";
-local lfs = require "lfs";
-
-local open = io.open;
-local t_concat = table.concat;
-local stat = lfs.attributes;
-
-local http_base = config.get("*", "core", "http_path") or "www_files";
-
-local response_400 = { status = "400 Bad Request", body = "<h1>Bad Request</h1>Sorry, we didn't understand your request :(" };
-local response_403 = { status = "403 Forbidden", body = "<h1>Forbidden</h1>You don't have permission to view the contents of this directory :(" };
-local response_404 = { status = "404 Not Found", body = "<h1>Page Not Found</h1>Sorry, we couldn't find what you were looking for :(" };
-
--- TODO: Should we read this from /etc/mime.types if it exists? (startup time...?)
-local mime_map = {
- html = "text/html";
- htm = "text/html";
- xml = "text/xml";
- xsl = "text/xml";
- txt = "text/plain; charset=utf-8";
- js = "text/javascript";
- css = "text/css";
-};
-
-local function preprocess_path(path)
- if path:sub(1,1) ~= "/" then
- path = "/"..path;
- end
- local level = 0;
- for component in path:gmatch("([^/]+)/") do
- if component == ".." then
- level = level - 1;
- elseif component ~= "." then
- level = level + 1;
- end
- if level < 0 then
- return nil;
- end
- end
- return path;
-end
-
-function serve_file(path)
- local full_path = http_base..path;
- if stat(full_path, "mode") == "directory" then
- if stat(full_path.."/index.html", "mode") == "file" then
- return serve_file(path.."/index.html");
- end
- return response_403;
- end
- local f, err = open(full_path, "rb");
- if not f then return response_404; end
- local data = f:read("*a");
- f:close();
- if not data then
- return response_403;
- end
- local ext = path:match("%.([^.]*)$");
- local mime = mime_map[ext]; -- Content-Type should be nil when not known
- return {
- headers = { ["Content-Type"] = mime; };
- body = data;
- };
-end
-
-local function handle_file_request(method, body, request)
- local path = preprocess_path(request.url.path);
- if not path then return response_400; end
- path = path:gsub("^/[^/]+", ""); -- Strip /files/
- return serve_file(path);
-end
-
-local function handle_default_request(method, body, request)
- local path = preprocess_path(request.url.path);
- if not path then return response_400; end
- return serve_file(path);
-end
-
-local function setup()
- local ports = config.get(module.host, "core", "http_ports") or { 5280 };
- httpserver.set_default_handler(handle_default_request);
- httpserver.new_from_config(ports, handle_file_request, { base = "files" });
-end
-if prosody.start_time then -- already started
- setup();
-else
- prosody.events.add_handler("server-started", setup);
-end
diff --git a/plugins/mod_iq.lua b/plugins/mod_iq.lua
index 484a1f8f..55bf59c3 100644
--- a/plugins/mod_iq.lua
+++ b/plugins/mod_iq.lua
@@ -8,10 +8,8 @@
local st = require "util.stanza";
-local jid_split = require "util.jid".split;
local full_sessions = full_sessions;
-local bare_sessions = bare_sessions;
if module:get_host_type() == "local" then
module:hook("iq/full", function(data)
@@ -33,7 +31,7 @@ end
module:hook("iq/bare", function(data)
-- IQ to bare JID recieved
- local origin, stanza = data.origin, data.stanza;
+ local stanza = data.stanza;
local type = stanza.attr.type;
-- TODO fire post processing events
@@ -49,7 +47,7 @@ end);
module:hook("iq/self", function(data)
-- IQ to self JID recieved
- local origin, stanza = data.origin, data.stanza;
+ local stanza = data.stanza;
local type = stanza.attr.type;
if type == "get" or type == "set" then
@@ -64,7 +62,7 @@ end);
module:hook("iq/host", function(data)
-- IQ to a local host recieved
- local origin, stanza = data.origin, data.stanza;
+ local stanza = data.stanza;
local type = stanza.attr.type;
if type == "get" or type == "set" then
diff --git a/plugins/mod_message.lua b/plugins/mod_message.lua
index df317532..ebff2fe7 100644
--- a/plugins/mod_message.lua
+++ b/plugins/mod_message.lua
@@ -14,7 +14,6 @@ local st = require "util.stanza";
local jid_bare = require "util.jid".bare;
local jid_split = require "util.jid".split;
local user_exists = require "core.usermanager".user_exists;
-local t_insert = table.insert;
local function process_to_bare(bare, origin, stanza)
local user = bare_sessions[bare];
diff --git a/plugins/mod_motd.lua b/plugins/mod_motd.lua
index d567288b..39b74de9 100644
--- a/plugins/mod_motd.lua
+++ b/plugins/mod_motd.lua
@@ -13,17 +13,18 @@ local motd_jid = module:get_option_string("motd_jid", host);
if not motd_text then return; end
+local jid_join = require "util.jid".join;
local st = require "util.stanza";
motd_text = motd_text:gsub("^%s*(.-)%s*$", "%1"):gsub("\n%s+", "\n"); -- Strip indentation from the config
-module:hook("resource-bind",
- function (event)
- local session = event.session;
- local motd_stanza =
- st.message({ to = session.username..'@'..session.host, from = motd_jid })
- :tag("body"):text(motd_text);
- core_route_stanza(hosts[host], motd_stanza);
- module:log("debug", "MOTD send to user %s@%s", session.username, session.host);
-
-end);
+module:hook("presence/bare", function (event)
+ local session, stanza = event.origin, event.stanza;
+ if not session.presence and not stanza.attr.type then
+ local motd_stanza =
+ st.message({ to = session.full_jid, from = motd_jid })
+ :tag("body"):text(motd_text);
+ core_route_stanza(hosts[host], motd_stanza);
+ module:log("debug", "MOTD send to user %s", session.full_jid);
+ end
+end, 1);
diff --git a/plugins/mod_posix.lua b/plugins/mod_posix.lua
index b388fb9d..1670ac95 100644
--- a/plugins/mod_posix.lua
+++ b/plugins/mod_posix.lua
@@ -112,7 +112,7 @@ end
local syslog_opened;
function syslog_sink_maker(config)
if not syslog_opened then
- pposix.syslog_open("prosody");
+ pposix.syslog_open("prosody", module:get_option_string("syslog_facility"));
syslog_opened = true;
end
local syslog, format = pposix.syslog_log, string.format;
diff --git a/plugins/mod_proxy65.lua b/plugins/mod_proxy65.lua
index d02f3b58..b2f6f703 100644
--- a/plugins/mod_proxy65.lua
+++ b/plugins/mod_proxy65.lua
@@ -6,35 +6,21 @@
-- 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>);
-]]--
+module:set_global();
-local module = module;
-local tostring = tostring;
local jid_compare, jid_prep = require "util.jid".compare, require "util.jid".prep;
local st = require "util.stanza";
-local connlisteners = require "net.connlisteners";
local sha1 = require "util.hashes".sha1;
-local server = require "net.server";
local b64 = require "util.encodings".base64.encode;
+local server = require "net.server";
-local host, name = module:get_host(), "SOCKS5 Bytestreams Service";
-local sessions, transfers = {}, {};
-
-local proxy_port = module:get_option("proxy65_port") or 5000;
-local proxy_interface = module:get_option("proxy65_interface") or "*";
-local proxy_address = module:get_option("proxy65_address") or (proxy_interface ~= "*" and proxy_interface) or host;
-local proxy_acl = module:get_option("proxy65_acl");
+local sessions, transfers = module:shared("sessions", "transfers");
local max_buffer_size = 4096;
-local connlistener = { default_port = proxy_port, default_interface = proxy_interface, default_mode = "*a" };
+local listener = {};
-function connlistener.onincoming(conn, data)
+function listener.onincoming(conn, data)
local session = sessions[conn] or {};
local transfer = transfers[session.sha];
@@ -84,7 +70,7 @@ function connlistener.onincoming(conn, data)
end
end
-function connlistener.ondisconnect(conn, err)
+function listener.ondisconnect(conn, err)
local session = sessions[conn];
if session then
if transfers[session.sha] then
@@ -101,88 +87,93 @@ function connlistener.ondisconnect(conn, err)
end
end
-module:add_identity("proxy", "bytestreams", name);
-module:add_feature("http://jabber.org/protocol/bytestreams");
-
-module:hook("iq-get/host/http://jabber.org/protocol/disco#info:query", function(event)
- local origin, stanza = event.origin, event.stanza;
- origin.send(st.reply(stanza):query("http://jabber.org/protocol/disco#info")
- :tag("identity", {category='proxy', type='bytestreams', name=name}):up()
- :tag("feature", {var="http://jabber.org/protocol/bytestreams"}) );
- return true;
-end, -1);
-
-module:hook("iq-get/host/http://jabber.org/protocol/disco#items:query", function(event)
- local origin, stanza = event.origin, event.stanza;
- origin.send(st.reply(stanza):query("http://jabber.org/protocol/disco#items"));
- return true;
-end, -1);
+function module.add_host(module)
+ local host, name = module:get_host(), module:get_option_string("name", "SOCKS5 Bytestreams Service");
+
+ local proxy_address = module:get_option("proxy65_address", host);
+ local proxy_port = module:get_option_number("proxy65_port", next(portmanager.get_active_services():search("proxy65", nil)[1] or {}));
+ local proxy_acl = module:get_option("proxy65_acl");
-module:hook("iq-get/host/http://jabber.org/protocol/bytestreams:query", function(event)
- local origin, stanza = event.origin, event.stanza;
+ module:add_identity("proxy", "bytestreams", name);
+ module:add_feature("http://jabber.org/protocol/bytestreams");
+
+ module:hook("iq-get/host/http://jabber.org/protocol/disco#info:query", function(event)
+ local origin, stanza = event.origin, event.stanza;
+ origin.send(st.reply(stanza):query("http://jabber.org/protocol/disco#info")
+ :tag("identity", {category='proxy', type='bytestreams', name=name}):up()
+ :tag("feature", {var="http://jabber.org/protocol/bytestreams"}) );
+ return true;
+ end, -1);
+
+ module:hook("iq-get/host/http://jabber.org/protocol/disco#items:query", function(event)
+ local origin, stanza = event.origin, event.stanza;
+ origin.send(st.reply(stanza):query("http://jabber.org/protocol/disco#items"));
+ return true;
+ end, -1);
- -- check ACL
- while proxy_acl and #proxy_acl > 0 do -- using 'while' instead of 'if' so we can break out of it
- local jid = stanza.attr.from;
- for _, acl in ipairs(proxy_acl) do
- if jid_compare(jid, acl) then break; end
+ module:hook("iq-get/host/http://jabber.org/protocol/bytestreams:query", function(event)
+ local origin, stanza = event.origin, event.stanza;
+
+ -- check ACL
+ while proxy_acl and #proxy_acl > 0 do -- using 'while' instead of 'if' so we can break out of it
+ local jid = stanza.attr.from;
+ for _, acl in ipairs(proxy_acl) do
+ if jid_compare(jid, acl) then break; end
+ end
+ module:log("warn", "Denying use of proxy for %s", tostring(stanza.attr.from));
+ origin.send(st.error_reply(stanza, "auth", "forbidden"));
+ return true;
end
- module:log("warn", "Denying use of proxy for %s", tostring(stanza.attr.from));
- origin.send(st.error_reply(stanza, "auth", "forbidden"));
+
+ local sid = stanza.tags[1].attr.sid;
+ origin.send(st.reply(stanza):tag("query", {xmlns="http://jabber.org/protocol/bytestreams", sid=sid})
+ :tag("streamhost", {jid=host, host=proxy_address, port=proxy_port}));
return true;
- end
-
- local sid = stanza.tags[1].attr.sid;
- origin.send(st.reply(stanza):tag("query", {xmlns="http://jabber.org/protocol/bytestreams", sid=sid})
- :tag("streamhost", {jid=host, host=proxy_address, port=proxy_port}));
- return true;
-end);
-
-module.unload = function()
- connlisteners.deregister(module.host .. ':proxy65');
-end
-
-module:hook("iq-set/host/http://jabber.org/protocol/bytestreams:query", function(event)
- local origin, stanza = event.origin, event.stanza;
-
- local query = stanza.tags[1];
- local sid = query.attr.sid;
- local from = stanza.attr.from;
- local to = query:get_child_text("activate");
- local prepped_to = jid_prep(to);
-
- local info = "sid: "..tostring(sid)..", initiator: "..tostring(from)..", target: "..tostring(prepped_to or to);
- if prepped_to and sid then
- local sha = sha1(sid .. from .. prepped_to, true);
- if not transfers[sha] then
- module:log("debug", "Activation request has unknown session id; activation failed (%s)", info);
- origin.send(st.error_reply(stanza, "modify", "item-not-found"));
- elseif not transfers[sha].initiator then
- module:log("debug", "The sender was not connected to the proxy; activation failed (%s)", info);
- origin.send(st.error_reply(stanza, "cancel", "not-allowed", "The sender (you) is not connected to the proxy"));
- --elseif not transfers[sha].target then -- can't happen, as target is set when a transfer object is created
- -- module:log("debug", "The recipient was not connected to the proxy; activation failed (%s)", info);
- -- origin.send(st.error_reply(stanza, "cancel", "not-allowed", "The recipient is not connected to the proxy"));
- else -- if transfers[sha].initiator ~= nil and transfers[sha].target ~= nil then
- module:log("debug", "Transfer activated (%s)", info);
- transfers[sha].activated = true;
- transfers[sha].target:resume();
- transfers[sha].initiator:resume();
- origin.send(st.reply(stanza));
+ end);
+
+ module:hook("iq-set/host/http://jabber.org/protocol/bytestreams:query", function(event)
+ local origin, stanza = event.origin, event.stanza;
+
+ local query = stanza.tags[1];
+ local sid = query.attr.sid;
+ local from = stanza.attr.from;
+ local to = query:get_child_text("activate");
+ local prepped_to = jid_prep(to);
+
+ local info = "sid: "..tostring(sid)..", initiator: "..tostring(from)..", target: "..tostring(prepped_to or to);
+ if prepped_to and sid then
+ local sha = sha1(sid .. from .. prepped_to, true);
+ if not transfers[sha] then
+ module:log("debug", "Activation request has unknown session id; activation failed (%s)", info);
+ origin.send(st.error_reply(stanza, "modify", "item-not-found"));
+ elseif not transfers[sha].initiator then
+ module:log("debug", "The sender was not connected to the proxy; activation failed (%s)", info);
+ origin.send(st.error_reply(stanza, "cancel", "not-allowed", "The sender (you) is not connected to the proxy"));
+ --elseif not transfers[sha].target then -- can't happen, as target is set when a transfer object is created
+ -- module:log("debug", "The recipient was not connected to the proxy; activation failed (%s)", info);
+ -- origin.send(st.error_reply(stanza, "cancel", "not-allowed", "The recipient is not connected to the proxy"));
+ else -- if transfers[sha].initiator ~= nil and transfers[sha].target ~= nil then
+ module:log("debug", "Transfer activated (%s)", info);
+ transfers[sha].activated = true;
+ transfers[sha].target:resume();
+ transfers[sha].initiator:resume();
+ origin.send(st.reply(stanza));
+ end
+ elseif to and sid then
+ module:log("debug", "Malformed activation jid; activation failed (%s)", info);
+ origin.send(st.error_reply(stanza, "modify", "jid-malformed"));
+ else
+ module:log("debug", "Bad request; activation failed (%s)", info);
+ origin.send(st.error_reply(stanza, "modify", "bad-request"));
end
- elseif to and sid then
- module:log("debug", "Malformed activation jid; activation failed (%s)", info);
- origin.send(st.error_reply(stanza, "modify", "jid-malformed"));
- else
- module:log("debug", "Bad request; activation failed (%s)", info);
- origin.send(st.error_reply(stanza, "modify", "bad-request"));
- end
- return true;
-end);
-
-if not connlisteners.register(module.host .. ':proxy65', connlistener) then
- module:log("error", "mod_proxy65: Could not establish a connection listener. Check your configuration please.");
- module:log("error", "Possibly two proxy65 components are configured to share the same port.");
+ return true;
+ end);
end
-connlisteners.start(module.host .. ':proxy65');
+module:provides("net", {
+ default_port = 5000;
+ listener = listener;
+ multiplex = {
+ pattern = "^\5";
+ };
+});
diff --git a/plugins/mod_pubsub.lua b/plugins/mod_pubsub.lua
index dd787d32..2cbd7184 100644
--- a/plugins/mod_pubsub.lua
+++ b/plugins/mod_pubsub.lua
@@ -57,6 +57,7 @@ function handlers.get_items(origin, stanza, items)
for _, entry in pairs(results) do
data:add_child(entry);
end
+ local reply;
if data then
reply = st.reply(stanza)
:tag("pubsub", { xmlns = xmlns_pubsub })
diff --git a/plugins/s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua
index b0bd5b40..3dab30f9 100644
--- a/plugins/s2s/mod_s2s.lua
+++ b/plugins/mod_s2s/mod_s2s.lua
@@ -8,6 +8,10 @@
module:set_global();
+local prosody = prosody;
+local hosts = prosody.hosts;
+local core_process_stanza = core_process_stanza;
+
local tostring, type = tostring, type;
local t_insert = table.insert;
local xpcall, traceback = xpcall, debug.traceback;
@@ -20,6 +24,7 @@ local new_xmpp_stream = require "util.xmppstream".new;
local s2s_new_incoming = require "core.s2smanager".new_incoming;
local s2s_new_outgoing = require "core.s2smanager".new_outgoing;
local s2s_destroy_session = require "core.s2smanager".destroy_session;
+local s2s_mark_connected = require "core.s2smanager".mark_connected;
local uuid_gen = require "util.uuid".generate;
local cert_verify_identity = require "util.x509".verify_identity;
@@ -29,6 +34,8 @@ local connect_timeout = module:get_option_number("s2s_timeout", 60);
local sessions = module:shared("sessions");
+local log = module._log;
+
--- Handle stanzas to remote domains
local bouncy_stanzas = { message = true, presence = true, iq = true };
@@ -39,7 +46,7 @@ local function bounce_sendq(session, reason)
local dummy = {
type = "s2sin";
send = function(s)
- (session.log or log)("error", "Replying to to an s2s error reply, please report this! Traceback: %s", get_traceback());
+ (session.log or log)("error", "Replying to to an s2s error reply, please report this! Traceback: %s", traceback());
end;
dummy = true;
};
@@ -60,7 +67,8 @@ local function bounce_sendq(session, reason)
session.sendq = nil;
end
-module:hook("route/remote", function (event)
+-- Handles stanzas to existing s2s sessions
+function route_to_existing_session(event)
local from_host, to_host, stanza = event.from_host, event.to_host, event.stanza;
if not hosts[from_host] then
log("warn", "Attempt to send stanza from %s - a host we don't serve", from_host);
@@ -79,7 +87,7 @@ module:hook("route/remote", function (event)
return true;
elseif host.type == "local" or host.type == "component" then
log("error", "Trying to send a stanza to ourselves??")
- log("error", "Traceback: %s", get_traceback());
+ log("error", "Traceback: %s", traceback());
log("error", "Stanza: %s", tostring(stanza));
return false;
else
@@ -94,9 +102,10 @@ module:hook("route/remote", function (event)
return true;
end
end
-end, 200);
+end
-module:hook("route/remote", function (event)
+-- Create a new outgoing session for a stanza
+function route_to_new_session(event)
local from_host, to_host, stanza = event.from_host, event.to_host, event.stanza;
log("debug", "opening a new outgoing connection for this stanza");
local host_session = s2s_new_outgoing(from_host, to_host);
@@ -112,7 +121,16 @@ module:hook("route/remote", function (event)
return false;
end
return true;
-end, 100);
+end
+
+function module.add_host(module)
+ if module:get_option_boolean("disallow_s2s", false) then
+ module:log("warn", "The 'disallow_s2s' config option is deprecated, please see http://prosody.im/doc/s2s#disabling");
+ return nil, "This host has disallow_s2s set";
+ end
+ module:hook("route/remote", route_to_existing_session, 200);
+ module:hook("route/remote", route_to_new_session, 100);
+end
--- Helper to check that a session peer's certificate is valid
local function check_cert_status(session)
@@ -127,6 +145,9 @@ local function check_cert_status(session)
-- Is there any interest in printing out all/the number of errors here?
if not chain_valid then
(session.log or log)("debug", "certificate chain validation result: invalid");
+ for depth, t in ipairs(errors) do
+ (session.log or log)("debug", "certificate error(s) at depth %d: %s", depth-1, table.concat(t, ", "))
+ end
session.cert_chain_status = "invalid";
else
(session.log or log)("debug", "certificate chain validation result: valid");
@@ -156,7 +177,6 @@ local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
function stream_callbacks.streamopened(session, attr)
local send = session.sends2s;
- -- TODO: #29: SASL/TLS on s2s streams
session.version = tonumber(attr.version) or 0;
-- TODO: Rename session.secure to session.encrypted
@@ -193,21 +213,24 @@ function stream_callbacks.streamopened(session, attr)
return;
end
+ -- For convenience we'll put the sanitised values into these variables
+ to, from = session.to_host, session.from_host;
+
session.streamid = uuid_gen();
(session.log or log)("debug", "Incoming s2s received %s", st.stanza("stream:stream", attr):top_tag());
- if session.to_host then
- if not hosts[session.to_host] then
+ if to then
+ if not hosts[to] then
-- Attempting to connect to a host we don't serve
session:close({
condition = "host-unknown";
- text = "This host does not serve "..session.to_host
+ text = "This host does not serve "..to
});
return;
- elseif hosts[session.to_host].disallow_s2s then
+ elseif not hosts[to].modules.s2s then
-- Attempting to connect to a host that disallows s2s
session:close({
condition = "policy-violation";
- text = "Server-to-server communication is not allowed to this host";
+ text = "Server-to-server communication is disabled for this host";
});
return;
end
@@ -217,14 +240,14 @@ function stream_callbacks.streamopened(session, attr)
send("<?xml version='1.0'?>");
send(st.stanza("stream:stream", { xmlns='jabber:server', ["xmlns:db"]='jabber:server:dialback',
- ["xmlns:stream"]='http://etherx.jabber.org/streams', id=session.streamid, from=session.to_host, to=session.from_host, version=(session.version > 0 and "1.0" or nil) }):top_tag());
+ ["xmlns:stream"]='http://etherx.jabber.org/streams', id=session.streamid, from=to, to=from, version=(session.version > 0 and "1.0" or nil) }):top_tag());
if session.version >= 1.0 then
local features = st.stanza("stream:features");
- if session.to_host then
- hosts[session.to_host].events.fire_event("s2s-stream-features", { origin = session, features = features });
+ if to then
+ hosts[to].events.fire_event("s2s-stream-features", { origin = session, features = features });
else
- (session.log or log)("warn", "No 'to' on stream header from %s means we can't offer any features", session.from_host or "unknown host");
+ (session.log or log)("warn", "No 'to' on stream header from %s means we can't offer any features", from or "unknown host");
end
log("debug", "Sending stream features: %s", tostring(features));
@@ -261,7 +284,6 @@ function stream_callbacks.streamopened(session, attr)
end
end
session.notopen = nil;
- session.send = function(stanza) prosody.events.fire_event("route/remote", { from_host = session.to_host, to_host = session.from_host, stanza = stanza}) end;
end
function stream_callbacks.streamclosed(session)
@@ -270,7 +292,7 @@ function stream_callbacks.streamclosed(session)
end
function stream_callbacks.streamdisconnected(session, err)
- if err and err ~= "closed" then
+ if err and err ~= "closed" and session.direction == "outgoing" then
(session.log or log)("debug", "s2s connection attempt failed: %s", err);
if s2sout.attempt_connection(session, err) then
(session.log or log)("debug", "...so we're going to try another target");
@@ -440,7 +462,6 @@ function listener.onstatus(conn, status)
if status == "ssl-handshake-complete" then
local session = sessions[conn];
if session and session.direction == "outgoing" then
- local to_host, from_host = session.to_host, session.from_host;
session.log("debug", "Sending stream header...");
session:open_stream(session.from_host, session.to_host);
end
diff --git a/plugins/s2s/s2sout.lib.lua b/plugins/mod_s2s/s2sout.lib.lua
index af55b273..2ddc9299 100644
--- a/plugins/s2s/s2sout.lib.lua
+++ b/plugins/mod_s2s/s2sout.lib.lua
@@ -12,18 +12,22 @@ local portmanager = require "core.portmanager";
local wrapclient = require "net.server".wrapclient;
local initialize_filters = require "util.filters".initialize;
local idna_to_ascii = require "util.encodings".idna.to_ascii;
-local add_task = require "util.timer".add_task;
local new_ip = require "util.ip".new_ip;
local rfc3484_dest = require "util.rfc3484".destination;
local socket = require "socket";
+local adns = require "net.adns";
+local dns = require "net.dns";
local t_insert, t_sort, ipairs = table.insert, table.sort, ipairs;
local st = require "util.stanza";
-local s2s_new_outgoing = require "core.s2smanager".new_outgoing;
local s2s_destroy_session = require "core.s2smanager".destroy_session;
+local log = module._log;
+
local sources = {};
+local dns_timeout = module:get_option_number("dns_timeout", 15);
+dns.settimeout(dns_timeout);
local max_dns_depth = module:get_option_number("dns_max_depth", 3);
local s2sout = {};
@@ -67,7 +71,7 @@ function s2sout.initiate_connection(host_session)
buffer = {};
host_session.send_buffer = buffer;
end
- log("debug", "Buffering data on unconnected s2sout to %s", to_host);
+ log("debug", "Buffering data on unconnected s2sout to %s", tostring(host_session.to_host));
buffer[#buffer+1] = data;
log("debug", "Buffered item %d: %s", #buffer, tostring(data));
end
@@ -269,6 +273,10 @@ function s2sout.make_connect(host_session, connect_host, connect_port)
if connect_host.proto == "IPv4" then
conn, handler = socket.tcp();
else
+ if not socket.tcp6 then
+ log("warn", "Could not connect to "..to_host..". Your version of lua-socket does not support IPv6");
+ return false, "no-ipv6";
+ end
conn, handler = socket.tcp6();
end
diff --git a/plugins/mod_saslauth.lua b/plugins/mod_saslauth.lua
index 9c62e5ec..804db5f9 100644
--- a/plugins/mod_saslauth.lua
+++ b/plugins/mod_saslauth.lua
@@ -16,7 +16,6 @@ local base64 = require "util.encodings".base64;
local cert_verify_identity = require "util.x509".verify_identity;
-local nodeprep = require "util.encodings".stringprep.nodeprep;
local usermanager_get_sasl_handler = require "core.usermanager".get_sasl_handler;
local tostring = tostring;
@@ -27,7 +26,6 @@ local log = module._log;
local xmlns_sasl ='urn:ietf:params:xml:ns:xmpp-sasl';
local xmlns_bind ='urn:ietf:params:xml:ns:xmpp-bind';
-local xmlns_stanzas ='urn:ietf:params:xml:ns:xmpp-stanzas';
local function build_reply(status, ret, err_msg)
local reply = st.stanza(status, {xmlns = xmlns_sasl});
@@ -51,15 +49,14 @@ local function handle_status(session, status, ret, err_msg)
module:fire_event("authentication-failure", { session = session, condition = ret, text = err_msg });
session.sasl_handler = session.sasl_handler:clean_clone();
elseif status == "success" then
- module:fire_event("authentication-success", { session = session });
- local username = nodeprep(session.sasl_handler.username);
-
local ok, err = sm_make_authenticated(session, session.sasl_handler.username);
if ok then
+ module:fire_event("authentication-success", { session = session });
session.sasl_handler = nil;
session:reset_stream();
else
module:log("warn", "SASL succeeded but username was invalid");
+ module:fire_event("authentication-failure", { session = session, condition = "not-authorized", text = err });
session.sasl_handler = session.sasl_handler:clean_clone();
return "failure", "not-authorized", "User authenticated successfully, but username was invalid";
end
@@ -191,8 +188,10 @@ local function s2s_external_auth(session, stanza)
session.from_host = text;
end
session.sends2s(build_reply("success"))
- module:log("info", "Accepting SASL EXTERNAL identity from %s", text or session.from_host);
- s2s_make_authenticated(session, text or session.from_host)
+
+ local domain = text ~= "" and text or session.from_host;
+ module:log("info", "Accepting SASL EXTERNAL identity from %s", domain);
+ s2s_make_authenticated(session, domain);
session:reset_stream();
return true
end
diff --git a/plugins/mod_version.lua b/plugins/mod_version.lua
index 52d8d290..d35103b6 100644
--- a/plugins/mod_version.lua
+++ b/plugins/mod_version.lua
@@ -21,7 +21,7 @@ if not module:get_option("hide_os_type") then
version = "Windows";
else
local os_version_command = module:get_option("os_version_command");
- local ok pposix = pcall(require, "pposix");
+ local ok, pposix = pcall(require, "util.pposix");
if not os_version_command and (ok and pposix and pposix.uname) then
version = pposix.uname().sysname;
end
diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua
index 8ef7a5b3..43b8423d 100644
--- a/plugins/muc/mod_muc.lua
+++ b/plugins/muc/mod_muc.lua
@@ -69,10 +69,10 @@ for jid in pairs(persistent_rooms) do
local node = jid_split(jid);
local data = datamanager.load(node, muc_host, "config") or {};
local room = muc_new_room(jid, {
- history_length = max_history_messages;
+ max_history_length = max_history_messages;
});
room._data = data._data;
- room._data.history_length = max_history_messages; --TODO: Need to allow per-room with a global limit
+ room._data.max_history_length = max_history_messages; -- Overwrite old max_history_length in data with current settings
room._affiliations = data._affiliations;
room.route_stanza = room_route_stanza;
room.save = room_save;
@@ -80,7 +80,7 @@ for jid in pairs(persistent_rooms) do
end
local host_room = muc_new_room(muc_host, {
- history_length = max_history_messages;
+ max_history_length = max_history_messages;
});
host_room.route_stanza = room_route_stanza;
host_room.save = room_save;
@@ -131,7 +131,7 @@ function stanza_handler(event)
(restrict_room_creation == "admin" and is_admin(stanza.attr.from)) or
(restrict_room_creation == "local" and select(2, jid_split(stanza.attr.from)) == module.host:gsub("^[^%.]+%.", "")) then
room = muc_new_room(bare, {
- history_length = max_history_messages;
+ max_history_length = max_history_messages;
});
room.route_stanza = room_route_stanza;
room.save = room_save;
diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua
index 731f9e37..0203df26 100644
--- a/plugins/muc/muc.lib.lua
+++ b/plugins/muc/muc.lib.lua
@@ -9,7 +9,6 @@
local select = select;
local pairs, ipairs = pairs, ipairs;
-local datamanager = require "util.datamanager";
local datetime = require "util.datetime";
local dataform = require "util.dataforms";
@@ -19,7 +18,6 @@ local jid_bare = require "util.jid".bare;
local jid_prep = require "util.jid".prep;
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 setmetatable = setmetatable;
local base64 = require "util.encodings".base64;
@@ -133,12 +131,11 @@ function room_mt:broadcast_message(stanza, historic)
stanza = st.clone(stanza);
stanza.attr.to = "";
local stamp = datetime.datetime();
- local chars = #tostring(stanza);
stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = muc_domain, stamp = stamp}):up(); -- XEP-0203
stanza:tag("x", {xmlns = "jabber:x:delay", from = muc_domain, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated)
local entry = { stanza = stanza, stamp = stamp };
t_insert(history, entry);
- while #history > (self._data.history_length or default_history_length) do t_remove(history, 1) end
+ while #history > self._data.history_length do t_remove(history, 1) end
end
end
function room_mt:broadcast_except_nick(stanza, nick)
@@ -185,7 +182,6 @@ function room_mt:send_history(to, stanza)
local n = 0;
local charcount = 0;
- local stanzacount = 0;
for i=#history,1,-1 do
local entry = history[i];
@@ -339,6 +335,21 @@ end
function room_mt:get_changesubject()
return self._data.changesubject;
end
+function room_mt:get_historylength()
+ return self._data.history_length or default_history_length;
+end
+function room_mt:set_historylength(length)
+ if tonumber(length) == nil then
+ return
+ end
+ length = tonumber(length);
+ log("debug", "max_history_length %s", self._data.max_history_length or "nil");
+ if self._data.max_history_length and length > self._data.max_history_length then
+ length = self._data.max_history_length
+ end
+ self._data.history_length = length;
+end
+
function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc
local from, to = stanza.attr.from, stanza.attr.to;
@@ -608,6 +619,12 @@ function room_mt:get_form_layout()
type = 'boolean',
label = 'Make Room Members-Only?',
value = self:is_members_only()
+ },
+ {
+ name = 'muc#roomconfig_historylength',
+ type = 'text-single',
+ label = 'Maximum Number of History Messages Returned by Room',
+ value = tostring(self:get_historylength())
}
});
end
@@ -659,6 +676,11 @@ function room_mt:process_form(origin, stanza)
dirty = dirty or (self:get_changesubject() ~= (not changesubject and true or nil))
module:log('debug', 'changesubject=%s', changesubject and "true" or "false")
+ local historylength = fields['muc#roomconfig_historylength'];
+ dirty = dirty or (self:get_historylength() ~= (historylength and true or nil))
+ module:log('debug', 'historylength=%s', historylength)
+
+
local whois = fields['muc#roomconfig_whois'];
if not valid_whois[whois] then
origin.send(st.error_reply(stanza, 'cancel', 'bad-request', "Invalid value for 'whois'"));
@@ -677,6 +699,7 @@ function room_mt:process_form(origin, stanza)
self:set_persistent(persistent);
self:set_hidden(not public);
self:set_changesubject(changesubject);
+ self:set_historylength(historylength);
if self.save then self:save(true); end
origin.send(st.reply(stanza));
@@ -721,7 +744,11 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha
local xmlns = stanza.tags[1] and stanza.tags[1].attr.xmlns;
if stanza.name == "iq" then
if xmlns == "http://jabber.org/protocol/disco#info" and type == "get" then
- origin.send(self:get_disco_info(stanza));
+ if stanza.tags[1].attr.node then
+ origin.send(st.error_reply(stanza, "cancel", "feature-not-implemented"));
+ else
+ origin.send(self:get_disco_info(stanza));
+ end
elseif xmlns == "http://jabber.org/protocol/disco#items" and type == "get" then
origin.send(self:get_disco_items(stanza));
elseif xmlns == "http://jabber.org/protocol/muc#admin" then
@@ -828,7 +855,6 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha
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];
local occupant = self._occupants[current_nick];
if not occupant then -- not in room
@@ -848,7 +874,7 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha
origin.send(st.error_reply(stanza, "cancel", "forbidden"));
end
else
- self:broadcast_message(stanza, true);
+ self:broadcast_message(stanza, self:get_historylength() > 0);
end
stanza.attr.from = from;
end
@@ -1102,7 +1128,8 @@ function _M.new_room(jid, config)
_occupants = {};
_data = {
whois = 'moderators';
- history_length = (config and config.history_length);
+ history_length = (config and config.max_history_length) or default_history_length;
+ max_history_length = (config and config.max_history_length) or default_history_length;
};
_affiliations = {};
}, room_mt);