--- a/mod_admin_web/admin_web/mod_admin_web.lua Thu Nov 22 18:59:10 2012 +0000
+++ b/mod_admin_web/admin_web/mod_admin_web.lua Tue Mar 12 12:10:25 2013 +0000
@@ -22,27 +22,15 @@
local is_admin = require "core.usermanager".is_admin;
local pubsub = require "util.pubsub";
local jid_bare = require "util.jid".bare;
-local lfs = require "lfs";
-local open = io.open;
-local stat = lfs.attributes;
module:set_global();
local service = {};
-local http_base = module.path:gsub("/[^/]+$","") .. "/www_files/";
-
local xmlns_adminsub = "http://prosody.im/adminsub";
local xmlns_c2s_session = "http://prosody.im/streams/c2s";
local xmlns_s2s_session = "http://prosody.im/streams/s2s";
-local mime_map = {
- html = "text/html";
- xml = "text/xml";
- js = "text/javascript";
- css = "text/css";
-};
-
local idmap = {};
function add_client(session, host)
@@ -104,37 +92,14 @@
end
end
-function serve_file(event, 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(event, path.."/index.html");
- end
- return 403;
- end
-
- local f, err = open(full_path, "rb");
- if not f then
- return 404;
- end
-
- local data = f:read("*a");
- f:close();
- if not data then
- return 403;
- end
-
- local ext = path:match("%.([^.]*)$");
- event.response.headers.content_type = mime_map[ext]; -- Content-Type should be nil when not known
- return data;
-end
-
function module.add_host(module)
-- Dependencies
module:depends("bosh");
module:depends("admin_adhoc");
module:depends("http");
+ local serve_file = module:depends("http_files").serve {
+ path = module:get_directory() .. "/www_files";
+ };
-- Setup HTTP server
module:provides("http", {
@@ -149,12 +114,14 @@
});
-- Setup adminsub service
- local function simple_broadcast(node, jids, item)
- item = st.clone(item);
- item.attr.xmlns = nil; -- Clear the pubsub namespace
+ local function simple_broadcast(kind, node, jids, item)
+ if item then
+ item = st.clone(item);
+ item.attr.xmlns = nil; -- Clear the pubsub namespace
+ end
local message = st.message({ from = module.host, type = "headline" })
:tag("event", { xmlns = xmlns_adminsub .. "#event" })
- :tag("items", { node = node })
+ :tag(kind, { node = node })
:add_child(item);
for jid in pairs(jids) do
module:log("debug", "Sending notification to %s", jid);
--- a/mod_admin_web/admin_web/www_files/js/main.js Thu Nov 22 18:59:10 2012 +0000
+++ b/mod_admin_web/admin_web/www_files/js/main.js Tue Mar 12 12:10:25 2013 +0000
@@ -96,6 +96,7 @@
if (status == Strophe.Status.CONNECTING) {
log('Strophe is connecting.');
} else if (status == Strophe.Status.CONNFAIL) {
+ alert('Connection failed (Wrong host?)');
log('Strophe failed to connect.');
showConnect();
} else if (status == Strophe.Status.DISCONNECTING) {
@@ -104,6 +105,7 @@
log('Strophe is disconnected.');
showConnect();
} else if (status == Strophe.Status.AUTHFAIL) {
+ alert('Wrong username and/or password');
log('Authentication failed');
if (connection) {
connection.disconnect();
@@ -120,7 +122,12 @@
return false;
}
for (i = 0; i < items.length; i++) {
- $('#host').append('<option>' + $(items[i]).text() + '</option>');
+ var host = $(items[i]).text();
+ if (host == Strophe.getDomainFromJid(connection.jid)) {
+ $('#host').append('<option selected>' + host + '</option>');
+ } else {
+ $('#host').append('<option>' + host + '</option>');
+ }
}
showDisconnect();
adminsubHost = $(items[0]).text();
--- a/mod_auth_external/mod_auth_external.lua Thu Nov 22 18:59:10 2012 +0000
+++ b/mod_auth_external/mod_auth_external.lua Tue Mar 12 12:10:25 2013 +0000
@@ -10,7 +10,6 @@
--
-local nodeprep = require "util.encodings".stringprep.nodeprep;
--local process = require "process";
local lpc; pcall(function() lpc = require "lpc"; end);
@@ -81,8 +80,6 @@
function do_query(kind, username, password)
if not username then return nil, "not-acceptable"; end
- username = nodeprep(username);
- if not username then return nil, "jid-malformed"; end
local query = (password and "%s:%s:%s:%s" or "%s:%s:%s"):format(kind, username, host, password);
local len = #query
@@ -132,12 +129,7 @@
function provider.get_sasl_handler()
local testpass_authentication_profile = {
plain_test = function(sasl, username, password, realm)
- local prepped_username = nodeprep(username);
- if not prepped_username then
- log("debug", "NODEprep failed on username: %s", username);
- return "", nil;
- end
- return usermanager.test_password(prepped_username, realm, password), true;
+ return usermanager.test_password(username, realm, password), true;
end,
};
return new_sasl(host, testpass_authentication_profile);
--- a/mod_auth_internal_yubikey/mod_auth_internal_yubikey.lua Thu Nov 22 18:59:10 2012 +0000
+++ b/mod_auth_internal_yubikey/mod_auth_internal_yubikey.lua Tue Mar 12 12:10:25 2013 +0000
@@ -18,7 +18,6 @@
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;
@@ -106,12 +105,6 @@
local realm = module:get_option("sasl_realm") or module.host;
local getpass_authentication_profile = {
plain_test = function(sasl, username, password, realm)
- local prepped_username = nodeprep(username);
- if not prepped_username then
- log("debug", "NODEprep failed on username: %s", username);
- return false, nil;
- end
-
return usermanager.test_password(username, realm, password), true;
end
};
--- a/mod_auth_ldap/mod_auth_ldap.lua Thu Nov 22 18:59:10 2012 +0000
+++ b/mod_auth_ldap/mod_auth_ldap.lua Tue Mar 12 12:10:25 2013 +0000
@@ -1,6 +1,5 @@
local new_sasl = require "util.sasl".new;
-local nodeprep = require "util.encodings".stringprep.nodeprep;
local log = require "util.logger".init("auth_ldap");
local ldap_server = module:get_option("ldap_server") or "localhost";
@@ -42,12 +41,7 @@
function provider.get_sasl_handler()
local testpass_authentication_profile = {
plain_test = function(sasl, username, password, realm)
- local prepped_username = nodeprep(username);
- if not prepped_username then
- log("debug", "NODEprep failed on username: %s", username);
- return "", nil;
- end
- return provider.test_password(prepped_username, password), true;
+ return provider.test_password(username, password), true;
end
};
return new_sasl(module.host, testpass_authentication_profile);
--- a/mod_auth_ldap2/mod_auth_ldap.lua Thu Nov 22 18:59:10 2012 +0000
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,84 +0,0 @@
--- vim:sts=4 sw=4
-
--- Prosody IM
--- Copyright (C) 2008-2010 Matthew Wild
--- Copyright (C) 2008-2010 Waqas Hussain
--- Copyright (C) 2012 Rob Hoelz
---
--- This project is MIT/X11 licensed. Please see the
--- COPYING file in the source package for more information.
---
--- http://code.google.com/p/prosody-modules/source/browse/mod_auth_ldap/mod_auth_ldap.lua
--- adapted to use common LDAP store
-
-local ldap = module:require 'ldap';
-local new_sasl = require 'util.sasl'.new;
-local nodeprep = require 'util.encodings'.stringprep.nodeprep;
-local jsplit = require 'util.jid'.split;
-
-if not ldap then
- return;
-end
-
-local provider = {}
-
-function provider.test_password(username, password)
- return ldap.bind(username, password);
-end
-
-function provider.user_exists(username)
- local params = ldap.getparams()
-
- local filter = ldap.filter.combine_and(params.user.filter, params.user.usernamefield .. '=' .. username);
-
- return ldap.singlematch {
- base = params.user.basedn,
- filter = filter,
- };
-end
-
-function provider.get_password(username)
- return nil, "Passwords unavailable for LDAP.";
-end
-
-function provider.set_password(username, password)
- return nil, "Passwords unavailable for LDAP.";
-end
-
-function provider.create_user(username, password)
- return nil, "Account creation/modification not available with LDAP.";
-end
-
-function provider.get_sasl_handler()
- local testpass_authentication_profile = {
- plain_test = function(sasl, username, password, realm)
- local prepped_username = nodeprep(username);
- if not prepped_username then
- module:log("debug", "NODEprep failed on username: %s", username);
- return "", nil;
- end
- return provider.test_password(prepped_username, password), true;
- end,
- mechanisms = { PLAIN = true },
- };
- return new_sasl(module.host, testpass_authentication_profile);
-end
-
-function provider.is_admin(jid)
- local admin_config = ldap.getparams().admin;
-
- if not admin_config then
- return;
- end
-
- local ld = ldap:getconnection();
- local username = jsplit(jid);
- local filter = ldap.filter.combine_and(admin_config.filter, admin_config.namefield .. '=' .. username);
-
- return ldap.singlematch {
- base = admin_config.basedn,
- filter = filter,
- };
-end
-
-module:provides("auth", provider);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_auth_ldap2/mod_auth_ldap2.lua Tue Mar 12 12:10:25 2013 +0000
@@ -0,0 +1,78 @@
+-- vim:sts=4 sw=4
+
+-- Prosody IM
+-- Copyright (C) 2008-2010 Matthew Wild
+-- Copyright (C) 2008-2010 Waqas Hussain
+-- Copyright (C) 2012 Rob Hoelz
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+-- http://code.google.com/p/prosody-modules/source/browse/mod_auth_ldap/mod_auth_ldap.lua
+-- adapted to use common LDAP store
+
+local ldap = module:require 'ldap';
+local new_sasl = require 'util.sasl'.new;
+local jsplit = require 'util.jid'.split;
+
+if not ldap then
+ return;
+end
+
+local provider = {}
+
+function provider.test_password(username, password)
+ return ldap.bind(username, password);
+end
+
+function provider.user_exists(username)
+ local params = ldap.getparams()
+
+ local filter = ldap.filter.combine_and(params.user.filter, params.user.usernamefield .. '=' .. username);
+
+ return ldap.singlematch {
+ base = params.user.basedn,
+ filter = filter,
+ };
+end
+
+function provider.get_password(username)
+ return nil, "Passwords unavailable for LDAP.";
+end
+
+function provider.set_password(username, password)
+ return nil, "Passwords unavailable for LDAP.";
+end
+
+function provider.create_user(username, password)
+ return nil, "Account creation/modification not available with LDAP.";
+end
+
+function provider.get_sasl_handler()
+ local testpass_authentication_profile = {
+ plain_test = function(sasl, username, password, realm)
+ return provider.test_password(username, password), true;
+ end,
+ mechanisms = { PLAIN = true },
+ };
+ return new_sasl(module.host, testpass_authentication_profile);
+end
+
+function provider.is_admin(jid)
+ local admin_config = ldap.getparams().admin;
+
+ if not admin_config then
+ return;
+ end
+
+ local ld = ldap:getconnection();
+ local username = jsplit(jid);
+ local filter = ldap.filter.combine_and(admin_config.filter, admin_config.namefield .. '=' .. username);
+
+ return ldap.singlematch {
+ base = admin_config.basedn,
+ filter = filter,
+ };
+end
+
+module:provides("auth", provider);
--- a/mod_auth_sql/mod_auth_sql.lua Thu Nov 22 18:59:10 2012 +0000
+++ b/mod_auth_sql/mod_auth_sql.lua Tue Mar 12 12:10:25 2013 +0000
@@ -5,7 +5,6 @@
local log = require "util.logger".init("auth_sql");
local new_sasl = require "util.sasl".new;
-local nodeprep = require "util.encodings".stringprep.nodeprep;
local DBI = require "DBI"
local connection;
@@ -101,12 +100,7 @@
function provider.get_sasl_handler()
local profile = {
plain = function(sasl, username, realm)
- local prepped_username = nodeprep(username);
- if not prepped_username then
- module:log("debug", "NODEprep failed on username: %s", username);
- return "", nil;
- end
- local password = get_password(prepped_username);
+ local password = get_password(username);
if not password then return "", nil; end
return password, true;
end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_bidi/mod_bidi.lua Tue Mar 12 12:10:25 2013 +0000
@@ -0,0 +1,129 @@
+-- Bidirectional Server-to-Server Connections
+-- http://xmpp.org/extensions/xep-0288.html
+-- Copyright (C) 2013 Kim Alvefur
+--
+-- This file is MIT/X11 licensed.
+--
+local s2smanager = require"core.s2smanager";
+local add_filter = require "util.filters".add_filter;
+local st = require "util.stanza";
+local jid_split = require"util.jid".prepped_split;
+
+local xmlns_bidi_feature = "urn:xmpp:features:bidi"
+local xmlns_bidi = "urn:xmpp:bidi";
+local noop = function () end
+local core_process_stanza = prosody.core_process_stanza or core_process_stanza;
+local traceback = debug.traceback;
+
+local function handleerr(err) log("error", "Traceback[s2s]: %s: %s", tostring(err), traceback()); end
+local function handlestanza(session, stanza)
+ if stanza.attr.xmlns == "jabber:client" then --COMPAT: Prosody pre-0.6.2 may send jabber:client
+ stanza.attr.xmlns = nil;
+ end
+ -- stanza = session.filter("stanzas/in", stanza);
+ if stanza then
+ return xpcall(function () return core_process_stanza(session, stanza) end, handleerr);
+ end
+end
+
+local function new_bidi(origin)
+ local bidi_session, remote_host;
+ origin.log("debug", "Creating bidirectional session wrapper");
+ if origin.direction == "incoming" then -- then we create an "outgoing" bidirectional session
+ local conflicting_session = hosts[origin.to_host].s2sout[origin.from_host]
+ if conflicting_session then
+ conflicting_session.log("info", "We already have an outgoing connection to %s, closing it...", origin.from_host);
+ conflicting_session:close{ condition = "conflict", text = "Replaced by bidirectional stream" }
+ s2smanager.destroy_session(conflicting_session);
+ end
+ remote_host = origin.from_host;
+ bidi_session = s2smanager.new_outgoing(origin.to_host, origin.from_host)
+ else -- outgoing -- then we create an "incoming" bidirectional session
+ remote_host = origin.to_host;
+ bidi_session = s2smanager.new_incoming(origin.conn)
+ bidi_session.to_host = origin.from_host;
+ bidi_session.from_host = origin.to_host;
+ add_filter(origin, "stanzas/in", function(stanza)
+ if stanza.attr.xmlns ~= nil then return stanza end
+ local _, host = jid_split(stanza.attr.from);
+ if host ~= remote_host then return stanza end
+ handlestanza(bidi_session, stanza);
+ end, 1);
+ end
+ origin.bidi_session = bidi_session;
+ bidi_session.sends2s = origin.sends2s;
+ bidi_session.bounce_sendq = noop;
+ bidi_session.notopen = nil;
+ bidi_session.is_bidi = true;
+ bidi_session.bidi_session = false;
+ bidi_session.orig_session = origin;
+ bidi_session.secure = origin.secure;
+ bidi_session.cert_identity_status = origin.cert_identity_status;
+ bidi_session.cert_chain_status = origin.cert_chain_status;
+ bidi_session.close = function(...)
+ return origin.close(...);
+ end
+
+ bidi_session.log("info", "Bidirectional session established");
+ s2smanager.make_authenticated(bidi_session, remote_host);
+ return bidi_session;
+end
+
+-- Incoming s2s
+module:hook("s2s-stream-features", function(event)
+ local origin, features = event.origin, event.features;
+ if not origin.is_bidi and not hosts[module.host].s2sout[origin.from_host] then
+ module:log("debug", "Announcing support for bidirectional streams");
+ features:tag("bidi", { xmlns = xmlns_bidi_feature }):up();
+ end
+end);
+
+module:hook("stanza/urn:xmpp:bidi:bidi", function(event)
+ local origin = event.session or event.origin;
+ if not origin.is_bidi and not origin.bidi_session then
+ module:log("debug", "%s requested bidirectional stream", origin.from_host);
+ origin.do_bidi = true;
+ return true;
+ end
+end);
+
+-- Outgoing s2s
+module:hook("stanza/http://etherx.jabber.org/streams:features", function(event)
+ local origin = event.session or event.origin;
+ if not ( origin.bidi_session or origin.is_bidi or origin.do_bidi)
+ and event.stanza:get_child("bidi", xmlns_bidi_feature) then
+ module:log("debug", "%s supports bidirectional streams", origin.to_host);
+ origin.sends2s(st.stanza("bidi", { xmlns = xmlns_bidi }));
+ origin.do_bidi = true;
+ end
+end, 160);
+
+function enable_bidi(event)
+ local session = event.session;
+ if session.do_bidi and not ( session.is_bidi or session.bidi_session ) then
+ session.do_bidi = nil;
+ new_bidi(session);
+ end
+end
+
+module:hook("s2sin-established", enable_bidi);
+module:hook("s2sout-established", enable_bidi);
+
+function disable_bidi(event)
+ local session = event.session;
+ if session.bidi_session then
+ local bidi_session = session.bidi_session;
+ session.bidi_session, bidi_session.orig_session = nil, nil;
+ session.log("debug", "Tearing down bidirectional stream");
+ s2smanager.destroy_session(bidi_session, event.reason);
+ elseif session.orig_session then
+ local orig_session = session.orig_session;
+ orig_session.bidi_session, session.orig_session = nil, nil;
+ orig_session.log("debug", "Tearing down bidirectional stream");
+ s2smanager.destroy_session(orig_session, event.reason);
+ end
+end
+
+module:hook("s2sin-destroyed", disable_bidi);
+module:hook("s2sout-destroyed", disable_bidi);
+
--- a/mod_carbons/mod_carbons.lua Thu Nov 22 18:59:10 2012 +0000
+++ b/mod_carbons/mod_carbons.lua Tue Mar 12 12:10:25 2013 +0000
@@ -7,6 +7,7 @@
local jid_bare = require "util.jid".bare;
local xmlns_carbons = "urn:xmpp:carbons:2";
local xmlns_carbons_old = "urn:xmpp:carbons:1";
+local xmlns_carbons_really_old = "urn:xmpp:carbons:0";
local xmlns_forward = "urn:xmpp:forward:0";
local full_sessions, bare_sessions = full_sessions, bare_sessions;
@@ -27,6 +28,19 @@
module:hook("iq/self/"..xmlns_carbons_old..":disable", toggle_carbons);
module:hook("iq/self/"..xmlns_carbons_old..":enable", toggle_carbons);
+-- COMPAT :(
+if module:get_option_boolean("carbons_v0") then
+ module:hook("iq/self/"..xmlns_carbons_really_old..":carbons", function(event)
+ local origin, stanza = event.origin, event.stanza;
+ if stanza.attr.type == "set" then
+ local state = stanza.tags[1].attr.mode;
+ origin.want_carbons = state == "enable" and xmlns_carbons_really_old;
+ origin.send(st.reply(stanza));
+ return true;
+ end
+ end);
+end
+
local function message_handler(event, c2s)
local origin, stanza = event.origin, event.stanza;
local orig_type = stanza.attr.type;
@@ -76,7 +90,7 @@
local copy = st.clone(stanza);
copy.attr.xmlns = "jabber:client";
local carbon = st.message{ from = bare_jid, type = orig_type, }
- :tag(c2s and "sent" or "received", { xmlns = xmlns_carbons }):up()
+ :tag(c2s and "sent" or "received", { xmlns = xmlns_carbons })
:tag("forwarded", { xmlns = xmlns_forward })
:add_child(copy):reset();
@@ -86,6 +100,10 @@
:tag("forwarded", { xmlns = xmlns_forward })
:add_child(copy):reset();
+ -- COMPAT
+ local carbon_really_old = st.clone(stanza)
+ :tag(c2s and "sent" or "received", { xmlns = xmlns_carbons_really_old }):up()
+
user_sessions = user_sessions and user_sessions.sessions;
for _, session in pairs(user_sessions) do
-- Carbons are sent to resources that have enabled it
@@ -93,10 +111,14 @@
-- but not the resource that sent the message, or the one that it's directed to
and session ~= target_session
-- and isn't among the top resources that would receive the message per standard routing rules
- and (c2s or session.priority ~= top_priority) then
+ and (c2s or session.priority ~= top_priority)
+ -- don't send v0 carbons (or copies) for c2s
+ and (not c2s or session.want_carbons ~= xmlns_carbons_really_old) then
carbon.attr.to = session.full_jid;
module:log("debug", "Sending carbon to %s", session.full_jid);
- local carbon = session.want_carbons == xmlns_carbons_old and carbon_old or carbon; -- COMPAT
+ local carbon = session.want_carbons == xmlns_carbons_old and carbon_old -- COMPAT
+ or session.want_carbons == xmlns_carbons_really_old and carbon_really_old -- COMPAT
+ or carbon;
session.send(carbon);
end
end
@@ -107,6 +129,7 @@
end
-- Stanzas sent by local clients
+module:hook("pre-message/host", c2s_message_handler, 1);
module:hook("pre-message/bare", c2s_message_handler, 1);
module:hook("pre-message/full", c2s_message_handler, 1);
-- Stanzas to local clients
@@ -115,3 +138,6 @@
module:add_feature(xmlns_carbons);
module:add_feature(xmlns_carbons_old);
+if module:get_option_boolean("carbons_v0") then
+ module:add_feature(xmlns_carbons_really_old);
+end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_carbons_adhoc/mod_carbons_adhoc.lua Tue Mar 12 12:10:25 2013 +0000
@@ -0,0 +1,42 @@
+-- Implement a Adhoc command which will show a user
+-- the status of carbons generation in regard to his clients
+--
+-- Copyright (C) 2012 Michael Holzt
+--
+-- This file is MIT/X11 licensed.
+
+local st = require "util.stanza";
+local jid_bare = require "util.jid".bare;
+local adhoc_new = module:require "adhoc".new;
+local xmlns_carbons_v2 = "urn:xmpp:carbons:2";
+local xmlns_carbons_v1 = "urn:xmpp:carbons:1";
+local xmlns_carbons_v0 = "urn:xmpp:carbons:0";
+
+local bare_sessions = bare_sessions;
+
+local function adhoc_status(self, data, state)
+ local result;
+
+ local bare_jid = jid_bare(data.from);
+ local user_sessions = bare_sessions[bare_jid];
+
+ local result = "";
+
+ user_sessions = user_sessions and user_sessions.sessions;
+ for _, session in pairs(user_sessions) do
+ if session.full_jid then
+ result = result .. session.full_jid .. ": " ..
+ ( (session.want_carbons == xmlns_carbons_v2 and "v2" ) or
+ (session.want_carbons == xmlns_carbons_v1 and "v1" ) or
+ (session.want_carbons == xmlns_carbons_v0 and "v0" ) or
+ "none" ) .. "\n";
+ end
+ end
+
+ return { info = result, status = "completed" };
+end
+
+local status_desc = adhoc_new("Carbons: Get Status",
+ "mod_carbons_adhoc#status", adhoc_status);
+
+module:add_item("adhoc", status_desc);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_carbons_copies/mod_carbons_copies.lua Tue Mar 12 12:10:25 2013 +0000
@@ -0,0 +1,63 @@
+-- Send carbons v0 style copies of incoming messages to clients which
+-- are not (yet) capable of Message Carbons (XEP-0280).
+--
+-- This extension integrates with the mod_carbons plugin in such a way
+-- that a client capable of Message Carbons will not get a v0 copy.
+--
+-- This extension can be enabled for all users by default by setting
+-- carbons_copies_default = true.
+--
+-- Alternatively or additionally setting carbons_copies_adhoc = true
+-- will allow the user to enable or disable copies through Adhoc
+-- commands.
+--
+-- Copyright (C) 2012 Michael Holzt
+--
+-- This file is MIT/X11 licensed.
+
+local jid_split = require "util.jid".split;
+local dm_load = require "util.datamanager".load;
+local dm_store = require "util.datamanager".store;
+local adhoc_new = module:require "adhoc".new;
+local xmlns_carbons_v0 = "urn:xmpp:carbons:0";
+local storename = "mod_carbons_copies";
+
+local function toggle_copies(data, on)
+ local username, hostname, resource = jid_split(data.from);
+ dm_store(username, hostname, storename, { enabled = on });
+end
+
+local function adhoc_enable_copies(self, data, state)
+ toggle_copies(data, true);
+ return { info = "Copies are enabled for you now.\nPlease restart/reconnect clients.", status = "completed" };
+end
+
+local function adhoc_disable_copies(self, data, state)
+ toggle_copies(data, false);
+ return { info = "Copies are disabled for you now.\nPlease restart/reconnect clients.", status = "completed" };
+end
+
+module:hook("resource-bind", function(event)
+ local session = event.session;
+ local username, hostname, resource = jid_split(session.full_jid);
+
+ local store = dm_load(username, hostname, storename) or
+ { enabled =
+ module:get_option_boolean("carbons_copies_default") };
+
+ if store.enabled then
+ session.want_carbons = xmlns_carbons_v0;
+ module:log("debug", "%s enabling copies", session.full_jid);
+ end
+end);
+
+-- Adhoc-Support
+if module:get_option_boolean("carbons_copies_adhoc") then
+ local enable_desc = adhoc_new("Carbons: Enable Copies",
+ "mod_carbons_copies#enable", adhoc_enable_copies);
+ local disable_desc = adhoc_new("Carbons: Disable Copies",
+ "mod_carbons_copies#disable", adhoc_disable_copies);
+
+ module:add_item("adhoc", enable_desc);
+ module:add_item("adhoc", disable_desc);
+end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_http_dir_listing/http_dir_listing/mod_http_dir_listing.lua Tue Mar 12 12:10:25 2013 +0000
@@ -0,0 +1,58 @@
+-- Prosody IM
+-- Copyright (C) 2012 Kim Alvefur
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+module:set_global();
+local server = require"net.http.server";
+local lfs = require "lfs";
+local stat = lfs.attributes;
+local build_path = require"socket.url".build_path;
+local base64_encode = require"util.encodings".base64.encode;
+local tag = require"util.stanza".stanza;
+local template = require"util.template";
+
+local function get_resource(resource)
+ local fh = assert(module:load_resource(resource));
+ local data = fh:read"*a";
+ fh:close();
+ return data;
+end
+
+local dir_index_template = template(get_resource("resources/template.html"));
+local style = get_resource("resources/style.css"):gsub("url%((.-)%)", function(url)
+ --module:log("debug", "Inlineing %s", url);
+ return "url(data:image/png;base64,"..base64_encode(get_resource("resources/"..url))..")";
+end);
+
+local function generate_directory_index(path, full_path)
+ local filelist = tag("ul", { class = "filelist" } ):text"\n";
+ if path ~= "/" then
+ filelist:tag("li", { class = "parent directory" })
+ :tag("a", { href = "..", rel = "up" }):text("Parent Directory"):up():up():text"\n"
+ end
+ for file in lfs.dir(full_path) do
+ if file:sub(1,1) ~= "." then
+ local attr = stat(full_path..file) or {};
+ local path = { file };
+ path.is_directory = attr.mode == "directory";
+ filelist:tag("li", { class = attr.mode })
+ :tag("a", { href = build_path(path) }):text(file):up()
+ :up():text"\n";
+ end
+ end
+ return "<!DOCTYPE html>\n"..tostring(dir_index_template.apply{
+ path = path,
+ style = style,
+ filelist = filelist,
+ footer = "Prosody "..prosody.version,
+ });
+end
+
+module:hook_object_event(server, "directory-index", function (event)
+ local ok, data = pcall(generate_directory_index, event.path, event.full_path);
+ if ok then return data end
+ module:log("warn", data);
+end);
Binary file mod_http_dir_listing/http_dir_listing/resources/folder.png has changed
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_http_dir_listing/http_dir_listing/resources/style.css Tue Mar 12 12:10:25 2013 +0000
@@ -0,0 +1,10 @@
+
+body{background-color:#eeeeec;font-family:sans-serif;}
+h1{font-size:xx-large;}
+a:link,a:visited{color:#2e3436;text-decoration:none;}
+a:link:hover,a:visited:hover{color:#3465a4;}
+.filelist{background-color:white;padding:1em;list-style-position:inside;-moz-column-width:20em;-webkit-column-width:20em;-ms-column-width:20em;column-width:20em;}
+.file{list-style-image:url(text-x-generic.png);}
+.directory{list-style-image:url(folder.png);}
+.parent{list-style-image:url(user-home.png);}
+footer{margin-top:1ex;font-size:smaller;color:#babdb6;}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_http_dir_listing/http_dir_listing/resources/template.html Tue Mar 12 12:10:25 2013 +0000
@@ -0,0 +1,14 @@
+<html>
+ <head>
+ <title>Index of {path}</title>
+ <meta charset="utf-8"/>
+ <style>{style}</style>
+ </head>
+ <body>
+ <h1>Index of {path}</h1>
+
+ {filelist}
+
+ <footer>{footer}</footer>
+ </body>
+</html>
Binary file mod_http_dir_listing/http_dir_listing/resources/text-x-generic.png has changed
Binary file mod_http_dir_listing/http_dir_listing/resources/user-home.png has changed
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_incidents_handling/incidents_handling/incidents_handling.lib.lua Tue Mar 12 12:10:25 2013 +0000
@@ -0,0 +1,405 @@
+-- This contains the auxiliary functions for the Incidents Handling module.
+-- (C) 2012-2013, Marco Cirillo (LW.Org)
+
+local pairs, ipairs, os_date, string, table, tonumber = pairs, ipairs, os.date, string, table, tonumber
+
+local dataforms_new = require "util.dataforms".new
+local st = require "util.stanza"
+
+local xmlns_inc = "urn:xmpp:incident:2"
+local xmlns_iodef = "urn:ietf:params:xml:ns:iodef-1.0"
+local my_host = nil
+
+-- // Util and Functions //
+
+local function ft_str()
+ local d = os_date("%FT%T%z"):gsub("^(.*)(%+%d+)", function(dt, z)
+ if z == "+0000" then return dt.."Z" else return dt..z end
+ end)
+ return d
+end
+
+local function get_incident_layout(i_type)
+ local layout = {
+ title = (i_type == "report" and "Incident report form") or (i_type == "request" and "Request for assistance with incident form"),
+ instructions = "Started/Ended Time, Contacts, Sources and Targets of the attack are mandatory. See RFC 5070 for further format instructions.",
+ { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/commands" },
+
+ { name = "name", type = "hidden", value = my_host },
+ { name = "entity", type ="text-single", label = "Remote entity to query" },
+ { name = "started", type = "text-single", label = "Incident Start Time" },
+ { name = "ended", type = "text-single", label = "Incident Ended Time" },
+ { name = "reported", type = "hidden", value = ft_str() },
+ { name = "description", type = "text-single", label = "Description",
+ desc = "Description syntax is: <lang (in xml:lang format)> <short description>" },
+ { name = "contacts", type = "text-multi", label = "Contacts",
+ desc = "Contacts entries format is: <address> <type> <role> - separated by new lines" },
+ { name = "related", type = "text-multi", label = "Related Incidents",
+ desc = "Related incidents entries format is: <CSIRT's FQDN> <Incident ID> - separated by new lines" },
+ { name = "impact", type = "text-single", label = "Impact Assessment",
+ desc = "Impact assessment format is: <severity> <completion> <type>" },
+ { name = "sources", type = "text-multi", label = "Attack Sources",
+ desc = "Attack sources format is: <address> <category> <count> <count-type>" },
+ { name = "targets", type = "text-multi", label = "Attack Targets",
+ desc = "Attack target format is: <address> <category> <noderole>" }
+ }
+
+ if i_type == "request" then
+ table.insert(layout, {
+ name = "expectation",
+ type = "list-single",
+ label = "Expected action from remote entity",
+ value = {
+ { value = "nothing", label = "No action" },
+ { value = "contact-sender", label = "Contact us, regarding the incident" },
+ { value = "investigate", label = "Investigate the entities listed into the incident" },
+ { value = "block-host", label = "Block the involved accounts" },
+ { value = "other", label = "Other action, filling the description field is required" }
+ }})
+ table.insert(layout, { name = "description", type = "text-single", label = "Description" })
+ end
+
+ return dataforms_new(layout)
+end
+
+local function render_list(incidents)
+ local layout = {
+ title = "Stored Incidents List",
+ instructions = "You can select and view incident reports here, if a followup/response is possible it'll be noted in the step after selection.",
+ { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/commands" },
+ {
+ name = "ids",
+ type = "list-single",
+ label = "Stored Incidents",
+ value = {}
+ }
+ }
+
+ -- Render stored incidents list
+
+ for id in pairs(incidents) do
+ table.insert(layout[2].value, { value = id, label = id })
+ end
+
+ return dataforms_new(layout)
+end
+
+local function insert_fixed(t, item) table.insert(t, { type = "fixed", value = item }) end
+
+local function render_single(incident)
+ local layout = {
+ title = string.format("Incident ID: %s - Friendly Name: %s", incident.data.id.text, incident.data.id.name),
+ instructions = incident.data.desc.text,
+ { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/commands" }
+ }
+
+ insert_fixed(layout, "Start Time: "..incident.data.start_time)
+ insert_fixed(layout, "End Time: "..incident.data.end_time)
+ insert_fixed(layout, "Report Time: "..incident.data.report_time)
+
+ insert_fixed(layout, "Contacts --")
+ for _, contact in ipairs(incident.data.contacts) do
+ insert_fixed(layout, string.format("Role: %s Type: %s", contact.role, contact.type))
+ if contact.jid then insert_fixed(layout, "--> JID: "..contact.jid..(contact.xmlns and ", XMLNS: "..contact.xmlns or "")) end
+ if contact.email then insert_fixed(layout, "--> E-Mail: "..contact.email) end
+ if contact.telephone then insert_fixed(layout, "--> Telephone: "..contact.telephone) end
+ if contact.postaladdr then insert_fixed(layout, "--> Postal Address: "..contact.postaladdr) end
+ end
+
+ insert_fixed(layout, "Related Activity --")
+ for _, related in ipairs(incident.data.related) do
+ insert_fixed(layout, string.format("Name: %s ID: %s", related.name, related.text))
+ end
+
+ insert_fixed(layout, "Assessment --")
+ insert_fixed(layout, string.format("Language: %s Severity: %s Completion: %s Type: %s",
+ incident.data.assessment.lang, incident.data.assessment.severity, incident.data.assessment.completion, incident.data.assessment.type))
+
+ insert_fixed(layout, "Sources --")
+ for _, source in ipairs(incident.data.event_data.sources) do
+ insert_fixed(layout, string.format("Address: %s Counter: %s", source.address.text, source.counter.value))
+ end
+
+ insert_fixed(layout, "Targets --")
+ for _, target in ipairs(incident.data.event_data.targets) do
+ insert_fixed(layout, string.format("For NodeRole: %s", (target.noderole.cat == "ext-category" and target.noderole.ext) or targets.noderole.cat))
+ for _, address in ipairs(target.addresses) do
+ insert_fixed(layout, string.format("---> Address: %s Type: %s", address.text, (address.cat == "ext-category" and address.ext) or address.cat))
+ end
+ end
+
+ if incident.data.expectation then
+ insert_fixed(layout, "Expected Action: "..incident.data.expectation.action)
+ if incident.data.expectation.desc then
+ insert_fixed(layout, "Expected Action Description: "..incident.data.expectation.desc)
+ end
+ end
+
+ if incident.type == "request" and incident.status == "open" then
+ table.insert(layout, { name = "response-datetime", type = "hidden", value = ft_str() })
+ table.insert(layout, { name = "response", type = "text-single", label = "Respond to the request" })
+ end
+
+ return dataforms_new(layout)
+end
+
+local function get_type(var, typ)
+ if typ == "counter" then
+ local count_type, count_ext = var, nil
+ if count_type ~= "byte" or count_type ~= "packet" or count_type ~= "flow" or count_type ~= "session" or
+ count_type ~= "alert" or count_type ~= "message" or count_type ~= "event" or count_type ~= "host" or
+ count_type ~= "site" or count_type ~= "organization" then
+ count_ext = count_type
+ count_type = "ext-type"
+ end
+ return count_type, count_ext
+ elseif typ == "category" then
+ local cat, cat_ext = var, nil
+ if cat ~= "asn" or cat ~= "atm" or cat ~= "e-mail" or cat ~= "ipv4-addr" or
+ cat ~= "ipv4-net" or cat ~= "ipv4-net-mask" or cat ~= "ipv6-addr" or cat ~= "ipv6-net" or
+ cat ~= "ipv6-net-mask" or cat ~= "mac" then
+ cat_ext = cat
+ cat = "ext-category"
+ end
+ return cat, cat_ext
+ elseif type == "noderole" then
+ local noderole_ext = nil
+ if cat ~= "client" or cat ~= "server-internal" or cat ~= "server-public" or cat ~= "www" or
+ cat ~= "mail" or cat ~= "messaging" or cat ~= "streaming" or cat ~= "voice" or
+ cat ~= "file" or cat ~= "ftp" or cat ~= "p2p" or cat ~= "name" or
+ cat ~= "directory" or cat ~= "credential" or cat ~= "print" or cat ~= "application" or
+ cat ~= "database" or cat ~= "infra" or cat ~= "log" then
+ noderole_ext = true
+ end
+ return noderole_ext
+ end
+end
+
+local function do_tag_mapping(tag, object)
+ if tag.name == "IncidentID" then
+ object.id = { text = tag:get_text(), name = tag.attr.name }
+ elseif tag.name == "StartTime" then
+ object.start_time = tag:get_text()
+ elseif tag.name == "EndTime" then
+ object.end_time = tag:get_text()
+ elseif tag.name == "ReportTime" then
+ object.report_time = tag:get_text()
+ elseif tag.name == "Description" then
+ object.desc = { text = tag:get_text(), lang = tag.attr["xml:lang"] }
+ elseif tag.name == "Contact" then
+ local jid = tag:get_child("AdditionalData").tags[1]
+ local email = tag:get_child("Email")
+ local telephone = tag:get_child("Telephone")
+ local postaladdr = tag:get_child("PostalAddress")
+ if not object.contacts then
+ object.contacts = {}
+ object.contacts[1] = {
+ role = tag.attr.role,
+ ext_role = (tag.attr["ext-role"] and true) or nil,
+ type = tag.attr.type,
+ ext_type = (tag.attr["ext-type"] and true) or nil,
+ xmlns = jid.attr.xmlns,
+ jid = jid:get_text(),
+ email = email,
+ telephone = telephone,
+ postaladdr = postaladdr
+ }
+ else
+ object.contacts[#object.contacts + 1] = {
+ role = tag.attr.role,
+ ext_role = (tag.attr["ext-role"] and true) or nil,
+ type = tag.attr.type,
+ ext_type = (tag.attr["ext-type"] and true) or nil,
+ xmlns = jid.attr.xmlns,
+ jid = jid:get_text(),
+ email = email,
+ telephone = telephone,
+ postaladdr = postaladdr
+ }
+ end
+ elseif tag.name == "RelatedActivity" then
+ object.related = {}
+ for _, t in ipairs(tag.tags) do
+ if tag.name == "IncidentID" then
+ object.related[#object.related + 1] = { text = t:get_text(), name = tag.attr.name }
+ end
+ end
+ elseif tag.name == "Assessment" then
+ local impact = tag:get_child("Impact")
+ object.assessment = { lang = impact.attr.lang, severity = impact.attr.severity, completion = impact.attr.completion, type = impact.attr.type }
+ elseif tag.name == "EventData" then
+ local source = tag:get_child("Flow").tags[1]
+ local target = tag:get_child("Flow").tags[2]
+ local expectation = tag:get_child("Flow").tags[3]
+ object.event_data = { sources = {}, targets = {} }
+ for _, t in ipairs(source.tags) do
+ local addr = t:get_child("Address")
+ local cntr = t:get_child("Counter")
+ object.event_data.sources[#object.event_data.sources + 1] = {
+ address = { cat = addr.attr.category, ext = addr.attr["ext-category"], text = addr:get_text() },
+ counter = { type = cntr.attr.type, ext_type = cntr.attr["ext-type"], value = cntr:get_text() }
+ }
+ end
+ for _, entry in ipairs(target.tags) do
+ local noderole = { cat = entry:get_child("NodeRole").attr.category, ext = entry:get_child("NodeRole").attr["ext-category"] }
+ local current = #object.event_data.targets + 1
+ object.event_data.targets[current] = { addresses = {}, noderole = noderole }
+ for _, tag in ipairs(entry.tags) do
+ object.event_data.targets[current].addresses[#object.event_data.targets[current].addresses + 1] = { text = tag:get_text(), cat = tag.attr.category, ext = tag.attr["ext-category"] }
+ end
+ end
+ if expectation then
+ object.event_data.expectation = {
+ action = expectation.attr.action,
+ desc = expectation:get_child("Description") and expectation:get_child("Description"):get_text()
+ }
+ end
+ elseif tag.name == "History" then
+ object.history = {}
+ for _, t in ipairs(tag.tags) do
+ object.history[#object.history + 1] = {
+ action = t.attr.action,
+ date = t:get_child("DateTime"):get_text(),
+ desc = t:get_chilld("Description"):get_text()
+ }
+ end
+ end
+end
+
+local function stanza_parser(stanza)
+ local object = {}
+
+ if stanza:get_child("report", xmlns_inc) then
+ local report = st.clone(stanza):get_child("report", xmlns_inc):get_child("Incident", xmlns_iodef)
+ for _, tag in ipairs(report.tags) do do_tag_mapping(tag, object) end
+ elseif stanza:get_child("request", xmlns_inc) then
+ local request = st.clone(stanza):get_child("request", xmlns_inc):get_child("Incident", xmlns_iodef)
+ for _, tag in ipairs(request.tags) do do_tag_mapping(tag, object) end
+ elseif stanza:get_child("response", xmlns_inc) then
+ local response = st.clone(stanza):get_child("response", xmlns_inc):get_child("Incident", xmlns_iodef)
+ for _, tag in ipairs(response.tags) do do_tag_mapping(tag, object) end
+ end
+
+ return object
+end
+
+local function stanza_construct(id)
+ if not id then return nil
+ else
+ local object = incidents[id].data
+ local s_type = incidents[id].type
+ local stanza = st.iq():tag(s_type or "report", { xmlns = xmlns_inc })
+ stanza:tag("Incident", { xmlns = xmlns_iodef, purpose = incidents[id].purpose })
+ :tag("IncidentID", { name = object.id.name }):text(object.id.text):up()
+ :tag("StartTime"):text(object.start_time):up()
+ :tag("EndTime"):text(object.end_time):up()
+ :tag("ReportTime"):text(object.report_time):up()
+ :tag("Description", { ["xml:lang"] = object.desc.lang }):text(object.desc.text):up():up();
+
+ local incident = stanza:get_child(s_type, xmlns_inc):get_child("Incident", xmlns_iodef)
+
+ for _, contact in ipairs(object.contacts) do
+ incident:tag("Contact", { role = (contact.ext_role and "ext-role") or contact.role,
+ ["ext-role"] = (contact.ext_role and contact.role) or nil,
+ type = (contact.ext_type and "ext-type") or contact.type,
+ ["ext-type"] = (contact.ext_type and contact.type) or nil })
+ :tag("Email"):text(contact.email):up()
+ :tag("Telephone"):text(contact.telephone):up()
+ :tag("PostalAddress"):text(contact.postaladdr):up()
+ :tag("AdditionalData")
+ :tag("jid", { xmlns = contact.xmlns }):text(contact.jid):up():up():up()
+
+ end
+
+ incident:tag("RelatedActivity"):up();
+
+ for _, related in ipairs(object.related) do
+ incident:get_child("RelatedActivity")
+ :tag("IncidentID", { name = related.name }):text(related.text):up();
+ end
+
+ incident:tag("Assessment")
+ :tag("Impact", {
+ lang = object.assessment.lang,
+ severity = object.assessment.severity,
+ completion = object.assessment.completion,
+ type = object.assessment.type
+ }):up():up();
+
+ incident:tag("EventData")
+ :tag("Flow")
+ :tag("System", { category = "source" }):up()
+ :tag("System", { category = "target" }):up():up():up();
+
+ local e_data = incident:get_child("EventData")
+
+ local sources = e_data:get_child("Flow").tags[1]
+ local targets = e_data:get_child("Flow").tags[2]
+
+ for _, source in ipairs(object.event_data.sources) do
+ sources:tag("Node")
+ :tag("Address", { category = source.address.cat, ["ext-category"] = source.address.ext })
+ :text(source.address.text):up()
+ :tag("Counter", { type = source.counter.type, ["ext-type"] = source.counter.ext_type })
+ :text(source.counter.value):up():up();
+ end
+
+ for _, target in ipairs(object.event_data.targets) do
+ targets:tag("Node"):up() ; local node = targets.tags[#targets.tags]
+ for _, address in ipairs(target.addresses) do
+ node:tag("Address", { category = address.cat, ["ext-category"] = address.ext }):text(address.text):up();
+ end
+ node:tag("NodeRole", { category = target.noderole.cat, ["ext-category"] = target.noderole.ext }):up();
+ end
+
+ if object.event_data.expectation then
+ e_data:tag("Expectation", { action = object.event_data.expectation.action }):up();
+ if object.event_data.expectation.desc then
+ local expectation = e_data:get_child("Expectation")
+ expectation:tag("Description"):text(object.event_data.expectation.desc):up();
+ end
+ end
+
+ if object.history then
+ local history = incident:tag("History"):up();
+
+ for _, item in ipairs(object.history) do
+ history:tag("HistoryItem", { action = item.action })
+ :tag("DateTime"):text(item.date):up()
+ :tag("Description"):text(item.desc):up():up();
+ end
+ end
+
+ -- Sanitize contact empty tags
+ for _, tag in ipairs(incident) do
+ if tag.name == "Contact" then
+ for i, check in ipairs(tag) do
+ if (check.name == "Email" or check.name == "PostalAddress" or check.name == "Telephone") and
+ not check:get_text() then
+ table.remove(tag, i)
+ end
+ end
+ end
+ end
+
+ if s_type == "request" then stanza.attr.type = "get"
+ elseif s_type == "response" then stanza.attr.type = "set"
+ else stanza.attr.type = "set" end
+
+ return stanza
+ end
+end
+
+
+_M = {} -- wraps methods into the library.
+_M.ft_str = ft_str
+_M.get_incident_layout = get_incident_layout
+_M.render_list = render_list
+_M.render_single = render_single
+_M.get_type = get_type
+_M.stanza_parser = stanza_parser
+_M.stanza_construct = stanza_construct
+_M.set_my_host = function(host) my_host = host end
+
+return _M
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_incidents_handling/incidents_handling/mod_incidents_handling.lua Tue Mar 12 12:10:25 2013 +0000
@@ -0,0 +1,352 @@
+-- This plugin implements XEP-268 (Incidents Handling)
+-- (C) 2012-2013, Marco Cirillo (LW.Org)
+
+-- Note: Only part of the IODEF specifications are supported.
+
+module:depends("adhoc")
+
+local datamanager = require "util.datamanager"
+local dataforms_new = require "util.dataforms".new
+local st = require "util.stanza"
+local id_gen = require "util.uuid".generate
+
+local pairs, os_time = pairs, os.time
+
+local xmlns_inc = "urn:xmpp:incident:2"
+local xmlns_iodef = "urn:ietf:params:xml:ns:iodef-1.0"
+
+local my_host = module:get_host()
+local ih_lib = module:require("incidents_handling")
+ih_lib.set_my_host(my_host)
+incidents = {}
+
+local expire_time = module:get_option_number("incidents_expire_time", 0)
+
+-- Incidents Table Methods
+
+local _inc_mt = {} ; _inc_mt.__index = _inc_mt
+
+function _inc_mt:init()
+ self:clean() ; self:save()
+end
+
+function _inc_mt:clean()
+ if expire_time > 0 then
+ for id, incident in pairs(self) do
+ if ((os_time() - incident.time) > expire_time) and incident.status ~= "open" then
+ incident = nil
+ end
+ end
+ end
+end
+
+function _inc_mt:save()
+ if not datamanager.store("incidents", my_host, "incidents_store", incidents) then
+ module:log("error", "Failed to save the incidents store!")
+ end
+end
+
+function _inc_mt:add(stanza, report)
+ local data = ih_lib.stanza_parser(stanza)
+ local new_object = {
+ time = os_time(),
+ status = (not report and "open") or nil,
+ data = data
+ }
+
+ self[data.id.text] = new_object
+ self:clean() ; self:save()
+end
+
+function _inc_mt:new_object(fields, formtype)
+ local start_time, end_time, report_time = fields.started, fields.ended, fields.reported
+
+ local _desc, _contacts, _related, _impact, _sources, _targets = fields.description, fields.contacts, fields.related, fields.impact, fields.sources, fields.targets
+ local fail = false
+
+ local _lang, _dtext = _desc:match("^(%a%a)%s(.*)$")
+ if not _lang or not _dtext then return false end
+ local desc = { text = _dtext, lang = _lang }
+
+ local contacts = {}
+ for contact in _contacts:gmatch("[%w%p]+%s[%w%p]+%s[%w%p]+") do
+ local address, atype, role = contact:match("^([%w%p]+)%s([%w%p]+)%s([%w%p]+)$")
+ if not address or not atype or not role then fail = true ; break end
+ contacts[#contacts + 1] = {
+ role = role,
+ ext_role = (role ~= "creator" or role ~= "admin" or role ~= "tech" or role ~= "irt" or role ~= "cc" and true) or nil,
+ type = atype,
+ ext_type = (atype ~= "person" or atype ~= "organization" and true) or nil,
+ jid = (atype == "jid" and address) or nil,
+ email = (atype == "email" and address) or nil,
+ telephone = (atype == "telephone" and address) or nil,
+ postaladdr = (atype == "postaladdr" and address) or nil
+ }
+ end
+
+ local related = {}
+ if _related then
+ for related in _related:gmatch("[%w%p]+%s[%w%p]+") do
+ local fqdn, id = related:match("^([%w%p]+)%s([%w%p]+)$")
+ if fqdn and id then related[#related + 1] = { text = id, name = fqdn } end
+ end
+ end
+
+ local _severity, _completion, _type = _impact:match("^([%w%p]+)%s([%w%p]+)%s([%w%p]+)$")
+ local assessment = { lang = "en", severity = _severity, completion = _completion, type = _type }
+
+ local sources = {}
+ for source in _sources:gmatch("[%w%p]+%s[%w%p]+%s[%d]+%s[%w%p]+") do
+ local address, cat, count, count_type = source:match("^([%w%p]+)%s([%w%p]+)%s(%d+)%s([%w%p]+)$")
+ if not address or not cat or not count or not count_type then fail = true ; break end
+ local cat, cat_ext = ih_lib.get_type(cat, "category")
+ local count_type, count_ext = ih_lib.get_type(count_type, "counter")
+
+ sources[#sources + 1] = {
+ address = { cat = cat, ext = cat_ext, text = address },
+ counter = { type = count_type, ext_type = count_ext, value = count }
+ }
+ end
+
+ local targets, _preprocess = {}, {}
+ for target in _targets:gmatch("[%w%p]+%s[%w%p]+%s[%w%p]+") do
+ local address, cat, noderole, noderole_ext
+ local address, cat, noderole = target:match("^([%w%p]+)%s([%w%p]+)%s([%w%p]+)$")
+ if not address or not cat or not noderole then fail = true ; break end
+ cat, cat_ext = ih_lib.get_type(cat, "category")
+ noderole_ext = ih_lib.get_type(cat, "noderole")
+
+ if not _preprocess[noderole] then _preprocess[noderole] = { addresses = {}, ext = noderole_ext } end
+
+ _preprocess[noderole].addresses[#_preprocess[noderole].addresses + 1] = {
+ text = address, cat = cat, ext = cat_ext
+ }
+ end
+ for noderole, data in pairs(_preprocess) do
+ local nr_cat = (data.ext and "ext-category") or noderole
+ local nr_ext = (data.ext and noderole) or nil
+ targets[#targets + 1] = { addresses = data.addresses, noderole = { cat = nr_cat, ext = nr_ext } }
+ end
+
+ local new_object = {}
+ if not fail then
+ new_object["time"] = os_time()
+ new_object["status"] = (formtype == "request" and "open") or nil
+ new_object["type"] = formtype
+ new_object["data"] = {
+ id = { text = id_gen(), name = my_host },
+ start_time = start_time,
+ end_time = end_time,
+ report_time = report_time,
+ desc = desc,
+ contacts = contacts,
+ related = related,
+ assessment = assessment,
+ event_data = { sources = sources, targets = targets }
+ }
+
+ self[new_object.data.id.text] = new_object
+ self:clean() ; self:save()
+ return new_object.data.id.text
+ else return false end
+end
+
+-- // Handler Functions //
+
+local function report_handler(event)
+ local origin, stanza = event.origin, event.stanza
+
+ incidents:add(stanza, true)
+ return origin.send(st.reply(stanza))
+end
+
+local function inquiry_handler(event)
+ local origin, stanza = event.origin, event.stanza
+
+ local inc_id = stanza:get_child("inquiry", xmlns_inc):get_child("Incident", xmlns_iodef):get_child("IncidentID"):get_text()
+ if incidents[inc_id] then
+ module:log("debug", "Server %s queried for incident %s which we know about, sending it", stanza.attr.from, inc_id)
+ local report_iq = stanza_construct(incidents[inc_id])
+ report_iq.attr.from = stanza.attr.to
+ report_iq.attr.to = stanza.attr.from
+ report_iq.attr.type = "set"
+
+ origin.send(st.reply(stanza))
+ origin.send(report_iq)
+ return true
+ else
+ module:log("error", "Server %s queried for incident %s but we don't know about it", stanza.attr.from, inc_id)
+ origin.send(st.error_reply(stanza, "cancel", "item-not-found")) ; return true
+ end
+end
+
+local function request_handler(event)
+ local origin, stanza = event.origin, event.stanza
+
+ local req_id = stanza:get_child("request", xmlns_inc):get_child("Incident", xmlns_iodef):get_child("IncidentID"):get_text()
+ if not incidents[req_id] then
+ origin.send(st.error_reply(stanza, "cancel", "item-not-found")) ; return true
+ else
+ origin.send(st.reply(stanza)) ; return true
+ end
+end
+
+local function response_handler(event)
+ local origin, stanza = event.origin, event.stanza
+
+ local res_id = stanza:get_child("response", xmlns_inc):get_child("Incident", xmlns_iodef):get_child("IncidentID"):get_text()
+ if incidents[res_id] then
+ incidents[res_id] = nil
+ incidents:add(stanza, true)
+ origin.send(st.reply(stanza)) ; return true
+ else
+ origin.send(st.error_reply(stanza, "cancel", "item-not-found")) ; return true
+ end
+end
+
+local function results_handler(event) return true end -- TODO results handling
+
+-- // Adhoc Commands //
+
+local function list_incidents_command_handler(self, data, state)
+ local list_incidents_layout = ih_lib.render_list(incidents)
+
+ if state then
+ if state.step == 1 then
+ if data.action == "cancel" then
+ return { status = "canceled" }
+ elseif data.action == "prev" then
+ return { status = "executing", actions = { "next", default = "next" }, form = list_incidents_layout }, {}
+ end
+
+ local single_incident_layout = state.form_layout
+ local fields = single_incident_layout:data(data.form)
+
+ if fields.response then
+ incidents[state.id].status = "closed"
+
+ local iq_send = ih_lib.stanza_construct(incidents[state.id])
+ module:send(iq_send)
+ return { status = "completed", info = "Response sent." }
+ else
+ return { status = "completed" }
+ end
+ else
+ if data.action == "cancel" then return { status = "canceled" } end
+ local fields = list_incidents_layout:data(data.form)
+
+ if fields.ids then
+ local single_incident_layout = ih_lib.render_single(incidents[fields.ids])
+ return { status = "executing", actions = { "prev", "complete", default = "complete" }, form = single_incident_layout }, { step = 1, form_layout = single_incident_layout, id = fields.ids }
+ else
+ return { status = "completed", error = { message = "You need to select the report ID to continue." } }
+ end
+ end
+ else
+ return { status = "executing", actions = { "next", default = "next" }, form = list_incidents_layout }, {}
+ end
+end
+
+local function send_inquiry_command_handler(self, data, state)
+ local send_inquiry_layout = dataforms_new{
+ title = "Send an inquiry about an incident report to a host";
+ instructions = "Please specify both the server host and the incident ID.";
+
+ { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/commands" };
+ { name = "server", type = "text-single", label = "Server to inquiry" };
+ { name = "hostname", type = "text-single", label = "Involved incident host" };
+ { name = "id", type = "text-single", label = "Incident ID" };
+ }
+
+ if state then
+ if data.action == "cancel" then return { status = "canceled" } end
+ local fields = send_inquiry_layout:data(data.form)
+
+ if not fields.hostname or not fields.id or not fields.server then
+ return { status = "completed", error = { message = "You must supply the server to quest, the involved incident host and the incident ID." } }
+ else
+ local iq_send = st.iq({ from = my_host, to = fields.server, type = "get" })
+ :tag("inquiry", { xmlns = xmlns_inc })
+ :tag("Incident", { xmlns = xmlns_iodef, purpose = "traceback" })
+ :tag("IncidentID", { name = data.hostname }):text(fields.id):up():up():up()
+
+ module:log("debug", "Sending incident inquiry to %s", fields.server)
+ module:send(iq_send)
+ return { status = "completed", info = "Inquiry sent, if an answer can be obtained from the remote server it'll be listed between incidents." }
+ end
+ else
+ return { status = "executing", form = send_inquiry_layout }, "executing"
+ end
+end
+
+local function rr_command_handler(self, data, state, formtype)
+ local send_layout = ih_lib.get_incident_layout(formtype)
+ local err_no_fields = { status = "completed", error = { message = "You need to fill all fields, except the eventual related incident." } }
+ local err_proc = { status = "completed", error = { message = "There was an error processing your request, check out the syntax" } }
+
+ if state then
+ if data.action == "cancel" then return { status = "canceled" } end
+ local fields = send_layout:data(data.form)
+
+ if fields.started and fields.ended and fields.reported and fields.description and fields.contacts and
+ fields.impact and fields.sources and fields.targets and fields.entity then
+ if formtype == "request" and not fields.expectation then return err_no_fields end
+ local id = incidents:new_object(fields, formtype)
+ if not id then return err_proc end
+
+ local stanza = ih_lib.stanza_construct(id)
+ stanza.attr.from = my_host
+ stanza.attr.to = fields.entity
+ module:log("debug","Sending incident %s stanza to: %s", formtype, stanza.attr.to)
+ module:send(stanza)
+
+ return { status = "completed", info = string.format("Incident %s sent to %s.", formtype, fields.entity) }
+ else
+ return err_no_fields
+ end
+ else
+ return { status = "executing", form = send_layout }, "executing"
+ end
+end
+
+local function send_report_command_handler(self, data, state)
+ return rr_command_handler(self, data, state, "report")
+end
+
+local function send_request_command_handler(self, data, state)
+ return rr_command_handler(self, data, state, "request")
+end
+
+local adhoc_new = module:require "adhoc".new
+local list_incidents_descriptor = adhoc_new("List Incidents", xmlns_inc.."#list", list_incidents_command_handler, "admin")
+local send_inquiry_descriptor = adhoc_new("Send Incident Inquiry", xmlns_inc.."#send_inquiry", send_inquiry_command_handler, "admin")
+local send_report_descriptor = adhoc_new("Send Incident Report", xmlns_inc.."#send_report", send_report_command_handler, "admin")
+local send_request_descriptor = adhoc_new("Send Incident Request", xmlns_inc.."#send_request", send_request_command_handler, "admin")
+module:provides("adhoc", list_incidents_descriptor)
+module:provides("adhoc", send_inquiry_descriptor)
+module:provides("adhoc", send_report_descriptor)
+module:provides("adhoc", send_request_descriptor)
+
+-- // Hooks //
+
+module:hook("iq-set/host/urn:xmpp:incident:2:report", report_handler)
+module:hook("iq-get/host/urn:xmpp:incident:2:inquiry", inquiry_handler)
+module:hook("iq-get/host/urn:xmpp:incident:2:request", request_handler)
+module:hook("iq-set/host/urn:xmpp:incident:2:response", response_handler)
+module:hook("iq-result/host/urn:xmpp:incident:2", results_handler)
+
+-- // Module Methods //
+
+module.load = function()
+ if datamanager.load("incidents", my_host, "incidents_store") then incidents = datamanager.load("incidents", my_host, "incidents_store") end
+ setmetatable(incidents, _inc_mt) ; incidents:init()
+end
+
+module.save = function()
+ return { incidents = incidents }
+end
+
+module.restore = function(data)
+ incidents = data.incidents or {}
+ setmetatable(incidents, _inc_mt) ; incidents:init()
+end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_last_offline/mod_last_offline.lua Tue Mar 12 12:10:25 2013 +0000
@@ -0,0 +1,33 @@
+local datamanager = require "util.datamanager";
+local jid_split = require "util.jid".split;
+local time = os.time;
+local NULL = {};
+local host = module.host;
+
+module:hook("resource-unbind", function(event)
+ local session = event.session;
+ if session.username then
+ datamanager.store(session.username, host, "last_online", {
+ timestamp = time(),
+ });
+ end
+end);
+
+local function offline_stamp(event)
+ local stanza = event.stanza;
+ local node, to_host = jid_split(stanza.attr.from);
+ if to_host == host and event.origin == hosts[host] and stanza.attr.type == "unavailable" then
+ local timestamp = (datamanager.load(node, host, "last_online") or NULL).timestamp;
+ if timestamp then
+ stanza:tag("delay", {
+ xmlns = "urn:xmpp:delay",
+ from = host,
+ stamp = datetime.datetime(timestamp),
+ }):up();
+ end
+ end
+end
+
+module:hook("pre-presence/bare", offline_stamp);
+module:hook("pre-presence/full", offline_stamp);
+
--- a/mod_lib_ldap/README.md Thu Nov 22 18:59:10 2012 +0000
+++ b/mod_lib_ldap/README.md Tue Mar 12 12:10:25 2013 +0000
@@ -12,7 +12,7 @@
With that note in mind, you need to set 'allow\_unencrypted\_plain\_auth' to true in your configuration if
you want to use LDAP authentication.
-To enable LDAP authentication, set 'authentication' to 'ldap' in your configuration file.
+To enable LDAP authentication, set 'authentication' to 'ldap2' in your configuration file.
See also http://prosody.im/doc/authentication.
# LDAP Storage
--- a/mod_lib_ldap/dev/posix-users.ldif Thu Nov 22 18:59:10 2012 +0000
+++ b/mod_lib_ldap/dev/posix-users.ldif Tue Mar 12 12:10:25 2013 +0000
@@ -19,6 +19,10 @@
ou: Users
objectclass: organizationalUnit
+dn: ou=Admins,ou=Users,dc=example,dc=com
+ou: Admins
+objectclass: organizationalUnit
+
dn: uid=one,ou=Users,dc=example,dc=com
objectclass: posixAccount
objectclass: person
@@ -212,6 +216,28 @@
K7j+qA4/I0UUNElHxV4s0HW/D81vBcP54IeNWiYZP5ehNeM3NrP5rFImYZ6gUUVDirmsJNKxGsE6
9YZB/wABNTJkcEEfWiigpM6rwgyR6vDLJ/q4zvb6Dmiiioe5dj//2Q==
+dn: uid=six,ou=Admins,ou=Users,dc=example,dc=com
+objectclass: posixAccount
+objectclass: person
+uid: six
+uidNumber: 1005
+gidNumber: 1005
+sn: Testerson
+cn: Admin Testerson
+userPassword: 123456
+homeDirectory: /home/six
+
+dn: uid=seven,ou=Users,dc=example,dc=com
+objectclass: posixAccount
+objectclass: person
+uid: seven
+uidNumber: 1006
+gidNumber: 1006
+sn: User
+cn: Invalid User
+userPassword: 1234567
+homeDirectory: /home/seven
+
dn: cn=Everyone,ou=Groups,dc=example,dc=com
objectclass: posixGroup
cn: Everyone
--- a/mod_lib_ldap/dev/prosody-posix-ldap.cfg.lua Thu Nov 22 18:59:10 2012 +0000
+++ b/mod_lib_ldap/dev/prosody-posix-ldap.cfg.lua Tue Mar 12 12:10:25 2013 +0000
@@ -1,6 +1,6 @@
-- Use Include 'prosody-posix-ldap.cfg.lua' from prosody.cfg.lua to include this file
-authentication = 'ldap' -- Indicate that we want to use LDAP for authentication
-storage = 'ldap' -- Indicate that we want to use LDAP for roster/vcard storage
+authentication = 'ldap2' -- Indicate that we want to use LDAP for authentication
+storage = 'ldap' -- Indicate that we want to use LDAP for roster/vcard storage
ldap = {
hostname = 'localhost', -- LDAP server location
@@ -8,10 +8,10 @@
bind_password = 'prosody', -- Bind password (optional if anonymous bind is supported)
user = {
- basedn = 'ou=Users,dc=example,dc=com', -- The base DN where user records can be found
- filter = 'objectClass=posixAccount', -- Filter expression to find user records under basedn
- usernamefield = 'uid', -- The field that contains the user's ID (this will be the username portion of the JID)
- namefield = 'cn', -- The field that contains the user's full name (this will be the alias found in the roster)
+ basedn = 'ou=Users,dc=example,dc=com', -- The base DN where user records can be found
+ filter = '(&(objectClass=posixAccount)(!(uid=seven)))', -- Filter expression to find user records under basedn
+ usernamefield = 'uid', -- The field that contains the user's ID (this will be the username portion of the JID)
+ namefield = 'cn', -- The field that contains the user's full name (this will be the alias found in the roster)
},
groups = {
--- a/mod_lib_ldap/dev/t/00-login.t Thu Nov 22 18:59:10 2012 +0000
+++ b/mod_lib_ldap/dev/t/00-login.t Tue Mar 12 12:10:25 2013 +0000
@@ -11,9 +11,10 @@
'three',
'four',
'five',
+ 'six',
);
-plan tests => scalar(@users) + 2;
+plan tests => scalar(@users) + 3;
foreach my $username (@users) {
my $conn = TestConnection->new($username);
@@ -23,7 +24,7 @@
});
my $error = $conn->cond->recv;
- ok(! $error) or diag($error);
+ ok(! $error) or diag("$username login failed: $error");
}
do {
@@ -38,7 +39,7 @@
};
do {
- my $conn = TestConnection->new('six', password => '12345');
+ my $conn = TestConnection->new('invalid', password => '12345');
$conn->reg_cb(session_ready => sub {
$conn->cond->send;
@@ -47,3 +48,14 @@
my $error = $conn->cond->recv;
ok($error);
};
+
+do {
+ my $conn = TestConnection->new('seven', password => '1234567');
+
+ $conn->reg_cb(session_ready => sub {
+ $conn->cond->send;
+ });
+
+ my $error = $conn->cond->recv;
+ ok($error);
+};
--- a/mod_lib_ldap/dev/t/TestConnection.pm Thu Nov 22 18:59:10 2012 +0000
+++ b/mod_lib_ldap/dev/t/TestConnection.pm Tue Mar 12 12:10:25 2013 +0000
@@ -14,6 +14,8 @@
three => '34512',
four => '45123',
five => '51234',
+ six => '123456',
+ seven => '1234567',
);
sub new {
--- a/mod_lib_ldap/ldap.lib.lua Thu Nov 22 18:59:10 2012 +0000
+++ b/mod_lib_ldap/ldap.lib.lua Tue Mar 12 12:10:25 2013 +0000
@@ -177,7 +177,27 @@
-- XXX consider renaming this...it doesn't bind the current connection
function _M.bind(username, password)
- local who = format('%s=%s,%s', params.user.usernamefield, username, params.user.basedn);
+ local conn = _M.getconnection();
+ local filter = format('%s=%s', params.user.usernamefield, username);
+
+ if filter then
+ filter = _M.filter.combine_and(filter, params.user.filter);
+ end
+
+ local who = _M.singlematch {
+ attrs = params.user.usernamefield,
+ base = params.user.basedn,
+ filter = filter,
+ };
+
+ if who then
+ who = who.dn;
+ module:log('debug', '_M.bind - who: %s', who);
+ else
+ module:log('debug', '_M.bind - no DN found for username = %s', username);
+ return nil, format('no DN found for username = %s', username);
+ end
+
local conn, err = ldap.open_simple(params.hostname, who, password, params.use_tls);
if conn then
@@ -192,9 +212,10 @@
local ld = _M.getconnection();
query.sizelimit = 1;
- query.scope = 'onelevel';
+ query.scope = 'subtree';
for dn, attribs in ld:search(query) do
+ attribs.dn = dn;
return attribs;
end
end
--- a/mod_mam/mod_mam.lua Thu Nov 22 18:59:10 2012 +0000
+++ b/mod_mam/mod_mam.lua Tue Mar 12 12:10:25 2013 +0000
@@ -23,6 +23,7 @@
local tostring = tostring;
local time_now = os.time;
local m_min = math.min;
+local t_insert = table.insert;
local timestamp, timestamp_parse = require "util.datetime".datetime, require "util.datetime".parse;
local uuid = require "util.uuid".generate;
local default_max_items, max_max_items = 20, module:get_option_number("max_archive_query_results", 50);
@@ -165,9 +166,12 @@
local first, last, index;
local n = 0;
local start = qset and qset.index or 1;
+ local results = {};
+ -- An empty <before/> means: give the last n items. So we loop backwards.
+ local reverse = qset and qset.before or false;
module:log("debug", "Loaded %d items, about to filter", #data);
- for i=start,#data do
+ for i=(reverse and #data or start),(reverse and start or #data),(reverse and -1 or 1) do
local item = data[i];
local when, with, resource = item.when, item.with, item.resource;
local id = item.id;
@@ -195,7 +199,11 @@
local orig_stanza = st.deserialize(item.stanza);
orig_stanza.attr.xmlns = "jabber:client";
fwd_st:add_child(orig_stanza);
- origin.send(fwd_st);
+ if reverse then
+ t_insert(results, 1, fwd_st);
+ else
+ results[#results + 1] = fwd_st;
+ end
if not first then
index = i;
first = id;
@@ -219,13 +227,16 @@
break
end
end
+ for _,v in pairs(results) do
+ origin.send(v);
+ end
-- That's all folks!
module:log("debug", "Archive query %s completed", tostring(qid));
local reply = st.reply(stanza);
if last then
-- This is a bit redundant, isn't it?
- reply:query(xmlns_mam):add_child(rsm.generate{first = first, last = last, count = n});
+ reply:query(xmlns_mam):add_child(rsm.generate{first = (reverse and last or first), last = (reverse and first or last), count = n});
end
origin.send(reply);
return true
--- a/mod_register_json/mod_register_json.lua Thu Nov 22 18:59:10 2012 +0000
+++ b/mod_register_json/mod_register_json.lua Tue Mar 12 12:10:25 2013 +0000
@@ -69,7 +69,6 @@
return http_response(event, 400, "JSON Decoding failed.")
else
-- Decode JSON data and check that all bits are there else throw an error
- req_body = json_decode(body)
if req_body["username"] == nil or req_body["password"] == nil or req_body["host"] == nil or req_body["ip"] == nil then
module:log("debug", "%s supplied an insufficent number of elements or wrong elements for the JSON registration", user)
return http_response(event, 400, "Invalid syntax.")
@@ -86,7 +85,7 @@
-- And nodeprep the username
local username = nodeprep(req_body["username"])
if not username then
- module:log("debug", "%s supplied an username containing invalid characters: %s", user, username)
+ module:log("debug", "An username containing invalid characters was supplied: %s", user)
return http_response(event, 406, "Supplied username contains invalid characters, see RFC 6122.")
else
if not usermanager.user_exists(username, req_body["host"]) then
--- a/mod_s2s_never_encrypt_blacklist/mod_s2s_never_encrypt_blacklist.lua Thu Nov 22 18:59:10 2012 +0000
+++ b/mod_s2s_never_encrypt_blacklist/mod_s2s_never_encrypt_blacklist.lua Tue Mar 12 12:10:25 2013 +0000
@@ -1,18 +1,24 @@
-- Filter out servers which gets choppy and buggy when it comes to starttls.
-local bad_servers = module:get_option_set("tls_s2s_blacklist")
-local bad_servers_ip = module:get_option_set("tls_s2s_blacklist_ip")
+local bad_servers = module:get_option_set("tls_s2s_blacklist", {})
+local bad_servers_ip = module:get_option_set("tls_s2s_blacklist_ip", {})
+local libev = module:get_option_boolean("use_libevent")
local function disable_tls_for_baddies_in(event)
- if bad_servers:contains(event.origin.to_host) or bad_servers_ip:contains(event.origin.conn:ip())
- then event.origin.conn.starttls = nil end
+ local session = event.origin
+ if bad_servers:contains(session.from_host) or bad_servers_ip:contains(session.conn:ip()) then
+ module:log("debug", "disabling tls on incoming stream from %s...", tostring(session.from_host));
+ if libev then session.conn.starttls = false; else session.conn.starttls = nil; end
+ end
end
local function disable_tls_for_baddies_out(event)
- if bad_servers:contains(event.origin.from_host) or bad_servers_ip:contains(event.origin.conn:ip())
- then event.origin.conn.starttls = nil end
+ local session = event.origin
+ if bad_servers:contains(session.to_host) then
+ module:log("debug", "disabling tls on outgoing stream from %s...", tostring(session.to_host));
+ if libev then session.conn.starttls = false; else session.conn.starttls = nil; end
+ end
end
-module:hook("s2s-stream-features", disable_tls_for_baddies_out, 10)
-module:hook("stanza/http://etherx.jabber.org/streams:features", disable_tls_for_baddies_in, 510)
-
+module:hook("s2s-stream-features", disable_tls_for_baddies_in, 600)
+module:hook("stanza/http://etherx.jabber.org/streams:features", disable_tls_for_baddies_out, 600)
--- a/mod_service_directories/mod_service_directories.lua Thu Nov 22 18:59:10 2012 +0000
+++ b/mod_service_directories/mod_service_directories.lua Tue Mar 12 12:10:25 2013 +0000
@@ -14,6 +14,7 @@
local adhoc_new = module:require "adhoc".new;
local to_ascii = require "util.encodings".idna.to_ascii;
local nameprep = require "util.encodings".stringprep.nameprep;
+local dataforms_new = require "util.dataforms".new;
local pairs, ipairs = pairs, ipairs;
local module = module;
local hosts = hosts;
@@ -79,7 +80,7 @@
-- Admin ad-hoc command to subscribe
local function add_contact_handler(self, data, state)
- local layout = {
+ local layout = dataforms_new{
title = "Adding a Server Buddy";
instructions = "Fill out this form to add a \"server buddy\".";
@@ -92,7 +93,7 @@
elseif data.action == "canceled" then
return { status = "canceled" };
else
- local fields = layout:data(data);
+ local fields = layout:data(data.form);
local peerjid = nameprep(fields.peerjid);
if not peerjid or peerjid == "" or #peerjid > 1023 or not to_ascii(peerjid) then
return { status = "completed", error = { message = "Invalid JID" } };
--- a/mod_smacks/mod_smacks.lua Thu Nov 22 18:59:10 2012 +0000
+++ b/mod_smacks/mod_smacks.lua Tue Mar 12 12:10:25 2013 +0000
@@ -83,6 +83,11 @@
queue[#queue+1] = cached_stanza;
end
+ if session.hibernating then
+ -- The session is hibernating, no point in sending the stanza
+ -- over a dead connection. It will be delivered upon resumption.
+ return true;
+ end
local ok, err = _send(stanza);
if ok and #queue > max_unacked_stanzas and not session.awaiting_ack and attr and not attr.xmlns then
session.awaiting_ack = true;
@@ -203,7 +208,7 @@
module:hook("pre-resource-unbind", function (event)
local session, err = event.session, event.error;
- if session.smacks and err ~= "session closed" then
+ if session.smacks and err then
if not session.resumption_token then
local queue = session.outgoing_stanza_queue;
if #queue > 0 then
@@ -244,6 +249,14 @@
end);
module:hook_stanza(xmlns_sm, "resume", function (session, stanza)
+ if session.full_jid then
+ session.log("debug", "Tried to resume after resource binding");
+ session.send(st.stanza("failed", sm_attr)
+ :tag("unexpected-request", { xmlns = xmlns_errors })
+ );
+ return true;
+ end
+
local id = stanza.attr.previd;
local original_session = session_registry[id];
if not original_session then
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_strict_https/mod_strict_https.lua Tue Mar 12 12:10:25 2013 +0000
@@ -0,0 +1,44 @@
+-- HTTP Strict Transport Security
+-- https://tools.ietf.org/html/rfc6797
+
+module:set_global();
+
+local http_server = require "net.http.server";
+
+local hsts_header = module:get_option_string("hsts_header", "max-age=31556952"); -- This means "Don't even try to access without HTTPS for a year"
+
+local _old_send_response;
+local _old_fire_event;
+
+local modules = {};
+
+function module.load()
+ _old_send_response = http_server.send_response;
+ function http_server.send_response(response, body)
+ response.headers.strict_transport_security = hsts_header;
+ return _old_send_response(response, body);
+ end
+
+ _old_fire_event = http_server._events.fire_event;
+ function http_server._events.fire_event(event, payload)
+ local request = payload.request;
+ local host = event:match("^[A-Z]+ ([^/]+)");
+ local module = modules[host];
+ if module and not request.secure then
+ payload.response.headers.location = module:http_url(request.path);
+ return 301;
+ end
+ return _old_fire_event(event, payload);
+ end
+end
+function module.unload()
+ http_server.send_response = _old_send_response;
+ http_server._events.fire_event = _old_fire_event;
+end
+function module.add_host(module)
+ local http_host = module:get_option_string("http_host", module.host);
+ modules[http_host] = module;
+ function module.unload()
+ modules[http_host] = nil;
+ end
+end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_uptime_presence/mod_uptime_presence.lua Tue Mar 12 12:10:25 2013 +0000
@@ -0,0 +1,17 @@
+local st = require"util.stanza";
+local datetime = require"util.datetime";
+
+local presence = st.presence({ from = module.host })
+ :tag("delay", { xmlns = "urn:xmpp:delay",
+ stamp = datetime.datetime(prosody.start_time) });
+
+module:hook("presence/host", function(event)
+ local stanza = event.stanza;
+ if stanza.attr.type == "probe" then
+ presence.attr.id = stanza.attr.id;
+ presence.attr.to = stanza.attr.from;
+ module:send(presence);
+ return true;
+ end
+end, 10);
+
--- a/mod_vjud/mod_vjud.lua Thu Nov 22 18:59:10 2012 +0000
+++ b/mod_vjud/mod_vjud.lua Tue Mar 12 12:10:25 2013 +0000
@@ -6,6 +6,8 @@
local jid_split = require "util.jid".prepped_split;
local vcard = module:require "vcard";
local rawget, rawset = rawget, rawset;
+local s_lower = string.lower;
+local s_find = string.find;
local st = require "util.stanza";
local template = require "util.template";
@@ -28,6 +30,8 @@
</item>
]];
+local search_mode = module:get_option_string("vjud_mode", "opt-in");
+local allow_remote = module:get_option_boolean("allow_remote_searches", search_mode ~= "all");
local base_host = module:get_option_string("vjud_search_domain",
module:get_host_type() == "component"
and module.host:gsub("^[^.]+%.","")
@@ -36,23 +40,6 @@
module:depends"disco";
module:add_feature("jabber:iq:search");
-local opted_in;
-function module.load()
- opted_in = dm_load(nil, module.host, "user_index") or {};
-end
-function module.unload()
- dm_store(nil, module.host, "user_index", opted_in);
-end
-
-local opt_in_layout = dataforms_new{
- title = "Search settings";
- instructions = "Do you want to appear in search results?";
- {
- name = "searchable",
- label = "Appear in search results?",
- type = "boolean",
- },
-};
local vCard_mt = {
__index = function(t, k)
if type(k) ~= "string" then return nil end
@@ -77,21 +64,32 @@
local at_host = "@"..base_host;
+local users; -- The user iterator
+
module:hook("iq/host/jabber:iq:search:query", function(event)
local origin, stanza = event.origin, event.stanza;
+ if not (allow_remote or origin.type == "c2s") then
+ origin.send(st.error_reply(stanza, "cancel", "not-allowed"))
+ return true;
+ end
+
if stanza.attr.type == "get" then
origin.send(st.reply(stanza):add_child(get_reply));
else -- type == "set"
local query = stanza.tags[1];
local first, last, nick, email =
- (query:get_child_text"first" or false),
- (query:get_child_text"last" or false),
- (query:get_child_text"nick" or false),
- (query:get_child_text"email" or false);
+ s_lower(query:get_child_text"first" or ""),
+ s_lower(query:get_child_text"last" or ""),
+ s_lower(query:get_child_text"nick" or ""),
+ s_lower(query:get_child_text"email" or "");
+ first = #first >= 2 and first;
+ last = #last >= 2 and last;
+ nick = #nick >= 2 and nick;
+ email = #email >= 2 and email;
if not ( first or last or nick or email ) then
- origin.send(st.error_reply(stanza, "modify", "not-acceptable", "All fields were empty"));
+ origin.send(st.error_reply(stanza, "modify", "not-acceptable", "All fields were empty or too short"));
return true;
end
@@ -110,13 +108,13 @@
});
end
else
- for username in pairs(opted_in) do
+ for username in users() do
local vCard = get_user_vcard(username);
- if vCard and (
- (vCard.N and vCard.N[2] == first) or
- (vCard.N and vCard.N[1] == last) or
- (vCard.NICKNAME and vCard.NICKNAME[1] == nick) or
- (vCard.EMAIL and vCard.EMAIL[1] == email)) then
+ if vCard
+ and ((first and vCard.N and s_find(s_lower(vCard.N[2]), first, nil, true))
+ or (last and vCard.N and s_find(s_lower(vCard.N[1]), last, nil, true))
+ or (nick and vCard.NICKNAME and s_find(s_lower(vCard.NICKNAME[1]), nick, nil, true))
+ or (email and vCard.EMAIL and s_find(s_lower(vCard.EMAIL[1]), email, nil, true))) then
reply:add_child(item_template.apply{
jid = username..at_host;
first = vCard.N and vCard.N[2] or nil;
@@ -132,30 +130,55 @@
return true;
end);
-local function opt_in_handler(self, data, state)
- local username, hostname = jid_split(data.from);
- if state then -- the second return value
- if data.action == "cancel" then
- return { status = "canceled" };
- end
-
- if not username or not hostname or hostname ~= base_host then
- return { status = "error", error = { type = "cancel",
- condition = "forbidden", message = "Invalid user or hostname." } };
- end
+if search_mode == "all" then
+ function users()
+ return usermanager.users(base_host);
+ end
+else -- if "opt-in", default
+ local opted_in;
+ function module.load()
+ opted_in = dm_load(nil, module.host, "user_index") or {};
+ end
+ function module.unload()
+ dm_store(nil, module.host, "user_index", opted_in);
+ end
+ function users()
+ return pairs(opted_in);
+ end
+ local opt_in_layout = dataforms_new{
+ title = "Search settings";
+ instructions = "Do you want to appear in search results?";
+ {
+ name = "searchable",
+ label = "Appear in search results?",
+ type = "boolean",
+ },
+ };
+ local function opt_in_handler(self, data, state)
+ local username, hostname = jid_split(data.from);
+ if state then -- the second return value
+ if data.action == "cancel" then
+ return { status = "canceled" };
+ end
- local fields = opt_in_layout:data(data.form);
- opted_in[username] = fields.searchable or nil
+ if not username or not hostname or hostname ~= base_host then
+ return { status = "error", error = { type = "cancel",
+ condition = "forbidden", message = "Invalid user or hostname." } };
+ end
- return { status = "completed" }
- else -- No state, send the form.
- return { status = "executing", actions = { "complete" },
+ local fields = opt_in_layout:data(data.form);
+ opted_in[username] = fields.searchable or nil
+
+ return { status = "completed" }
+ else -- No state, send the form.
+ return { status = "executing", actions = { "complete" },
form = { layout = opt_in_layout, values = { searchable = opted_in[username] } } }, true;
+ end
end
-end
-local adhoc_new = module:require "adhoc".new;
-local adhoc_vjudsetup = adhoc_new("Search settings", "vjudsetup", opt_in_handler);--, "self");-- and nil);
-module:depends"adhoc";
-module:provides("adhoc", adhoc_vjudsetup);
+ local adhoc_new = module:require "adhoc".new;
+ local adhoc_vjudsetup = adhoc_new("Search settings", "vjudsetup", opt_in_handler);--, "self");-- and nil);
+ module:depends"adhoc";
+ module:provides("adhoc", adhoc_vjudsetup);
+end
--- a/mod_websocket/mod_websocket.lua Thu Nov 22 18:59:10 2012 +0000
+++ b/mod_websocket/mod_websocket.lua Tue Mar 12 12:10:25 2013 +0000
@@ -1,6 +1,4 @@
-- Prosody IM
--- Copyright (C) 2008-2010 Matthew Wild
--- Copyright (C) 2008-2010 Waqas Hussain
-- Copyright (C) 2012 Florian Zeitz
--
-- This project is MIT/X11 licensed. Please see the
@@ -9,28 +7,19 @@
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 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 add_filter = require "util.filters".add_filter;
local sha1 = require "util.hashes".sha1;
local base64 = require "util.encodings".base64.encode;
-local band = require "bit".band;
-local bxor = require "bit".bxor;
-
-local xpcall, tostring, type = xpcall, tostring, type;
-local traceback = debug.traceback;
+local softreq = require "util.dependencies".softreq;
+local portmanager = require "core.portmanager";
-local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
-
-local log = module._log;
-
-local c2s_timeout = module:get_option_number("c2s_timeout");
-local stream_close_timeout = module:get_option_number("c2s_close_timeout", 5);
-local opt_keepalives = module:get_option_boolean("tcp_keepalives", false);
+local bit;
+pcall(function() bit = require"bit"; end);
+bit = bit or softreq"bit32"
+if not bit then module:log("error", "No bit module found. Either LuaJIT 2, lua-bitop or Lua 5.2 is required"); end
+local band = bit.band;
+local bxor = bit.bxor;
+local rshift = bit.rshift;
local cross_domain = module:get_option("cross_domain_websocket");
if cross_domain then
@@ -44,11 +33,9 @@
end
end
-local sessions = module:shared("sessions");
-local core_process_stanza = prosody.core_process_stanza;
-
-local stream_callbacks = { default_ns = "jabber:client", handlestanza = core_process_stanza };
-local listener = {};
+module:depends("c2s")
+local sessions = module:shared("c2s/sessions");
+local c2s_listener = portmanager.get_service("c2s").listener;
-- Websocket helpers
local function parse_frame(frame)
@@ -118,11 +105,11 @@
result = result .. string.char(length);
elseif length <= 0xFFFF then -- 2-byte length
result = result .. string.char(126);
- result = result .. string.char(length/0x100) .. string.char(length%0x100);
+ result = result .. string.char(rshift(length, 8)) .. string.char(length%0x100);
else -- 8-byte length
result = result .. string.char(127);
for i = 7, 0, -1 do
- result = result .. string.char(( length / (2^(8*i)) ) % 0x100);
+ result = result .. string.char(rshift(length, 8*i) % 0x100);
end
end
@@ -131,198 +118,33 @@
return result;
end
---- Stream events handlers
-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 = "" };
-
-function stream_callbacks.streamopened(session, attr)
- local send = session.send;
- session.host = nameprep(attr.to);
- if not session.host then
- session:close{ condition = "improper-addressing",
- text = "A valid 'to' attribute is required on stream headers" };
- return;
- end
- session.version = tonumber(attr.version) or 0;
- session.streamid = uuid_generate();
- (session.log or session)("debug", "Client sent opening <stream:stream> to %s", session.host);
+--- Filter stuff
+function handle_request(event, path)
+ local request, response = event.request, event.response;
+ local conn = response.conn;
- if not hosts[session.host] then
- -- We don't serve this host...
- session:close{ condition = "host-unknown", text = "This server does not serve "..tostring(session.host)};
- return;
- end
-
- -- COMPAT: Some current client implementations need this to be self-closing
- if session.self_closing_stream then
- send("<?xml version='1.0'?>"..tostring(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' })));
- else
- 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());
- end
-
- (session.log or log)("debug", "Sent reply <stream:stream> to client");
- session.notopen = nil;
-
- -- If session.secure is *false* (not nil) then it means we /were/ encrypting
- -- since we now have a new stream header, session is secured
- if session.secure == false then
- session.secure = true;
+ if not request.headers.sec_websocket_key then
+ response.headers.content_type = "text/html";
+ return [[<!DOCTYPE html><html><head><title>Websocket</title></head><body>
+ <p>It works! Now point your WebSocket client to this URL to connect to Prosody.</p>
+ </body></html>]];
end
- local features = st.stanza("stream:features");
- hosts[session.host].events.fire_event("stream-features", { origin = session, features = features });
- module:fire_event("stream-features", session, features);
-
- send(features);
-end
-
-function stream_callbacks.streamclosed(session)
- session.log("debug", "Received </stream:stream>");
- session:close(false);
-end
-
-function stream_callbacks.error(session, error, data)
- if error == "no-stream" then
- session.log("debug", "Invalid opening stream header");
- session:close("invalid-namespace");
- elseif error == "parse-error" then
- (session.log or log)("debug", "Client XML parse error: %s", 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
-end
-
-local function handleerr(err) log("error", "Traceback[c2s]: %s: %s", tostring(err), traceback()); end
-function stream_callbacks.handlestanza(session, stanza)
- stanza = session.filter("stanzas/in", stanza);
- if stanza then
- return xpcall(function () return core_process_stanza(session, stanza) end, handleerr);
- end
-end
+ local wants_xmpp = false;
+ (request.headers.sec_websocket_protocol or ""):gsub("([^,]*),?", function (proto)
+ if proto == "xmpp" then wants_xmpp = true; end
+ end);
---- Session methods
-local function session_close(session, reason)
- local log = session.log or log;
- if session.conn then
- if session.notopen then
- -- COMPAT: Some current client implementations need this to be self-closing
- if session.self_closing_stream then
- session.send("<?xml version='1.0'?>"..tostring(st.stanza("stream:stream", default_stream_attr)));
- else
- session.send("<?xml version='1.0'?>"..st.stanza("stream:stream", default_stream_attr):top_tag());
- end
- end
- if reason then -- nil == no err, initiated by us, false == initiated by client
- if type(reason) == "string" then -- assume stream error
- log("debug", "Disconnecting client, <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
- log("debug", "Disconnecting client, <stream:error> is: %s", tostring(stanza));
- session.send(stanza);
- elseif reason.name then -- a stanza
- log("debug", "Disconnecting client, <stream:error> is: %s", tostring(reason));
- session.send(reason);
- end
- end
- end
- session.send("</stream:stream>");
- function session.send() return false; end
-
- local reason = (reason and (reason.text or reason.condition)) or reason;
- session.log("info", "c2s stream for %s closed: %s", session.full_jid or ("<"..session.ip..">"), reason or "session closed");
-
- -- Authenticated incoming stream may still be sending us stanzas, so wait for </stream:stream> from remote
- local conn = session.conn;
- if reason == nil and not session.notopen and session.type == "c2s" then
- -- Grace time to process data from authenticated cleanly-closed stream
- add_task(stream_close_timeout, function ()
- if not session.destroyed then
- session.log("warn", "Failed to receive a stream close response, closing connection anyway...");
- sm_destroy_session(session, reason);
- conn:close();
- end
- end);
- else
- sm_destroy_session(session, reason);
- conn:close();
- end
- end
-end
-
-module:hook_global("user-deleted", function(event)
- local username, host = event.username, event.host;
- local user = hosts[host].sessions[username];
- if user and user.sessions then
- for jid, session in pairs(user.sessions) do
- session:close{ condition = "not-authorized", text = "Account deleted" };
- end
- end
-end, 200);
-
---- Port listener
-function listener.onconnect(conn)
- local session = sm_new_session(conn);
- sessions[conn] = session;
-
- session.log("info", "Client connected");
-
- -- Client is using legacy SSL (otherwise mod_tls sets this flag)
- if conn:ssl() then
- session.secure = true;
- end
-
- if opt_keepalives then
- conn:setoption("keepalive", opt_keepalives);
- end
-
- session.close = session_close;
-
- session.conn.starttls = nil;
-
- local stream = new_xmpp_stream(session, stream_callbacks);
- session.stream = stream;
- session.notopen = true;
-
- function session.reset_stream()
- session.notopen = true;
- session.stream:reset();
+ if not wants_xmpp then
+ return 501;
end
local function websocket_close(code, message)
- local data = string.char(code/0x100) .. string.char(code%0x100) .. message;
+ local data = string.char(rshift(code, 8)) .. string.char(code%0x100) .. message;
conn:write(build_frame({opcode = 0x8, FIN = true, data = data}));
conn:close();
end
- local filter = session.filter;
local dataBuffer;
local function handle_frame(frame)
module:log("debug", "Websocket received: %s (%i bytes)", frame.data, #frame.data);
@@ -365,115 +187,58 @@
dataBuffer = frame.data;
elseif frame.opcode == 0x2 then -- Binary frame
websocket_close(1003, "Only text frames are supported");
- return false;
+ return;
elseif frame.opcode == 0x8 then -- Close request
websocket_close(1000, "Goodbye");
- return false;
+ return;
elseif frame.opcode == 0x9 then -- Ping frame
frame.opcode = 0xA;
conn:write(build_frame(frame));
- return true;
+ return "";
else
log("warn", "Received frame with unsupported opcode %i", frame.opcode);
- return true;
+ return "";
end
if frame.FIN then
data = dataBuffer;
dataBuffer = nil;
- -- COMPAT: Some current client implementations send a self-closing <stream:stream>
- data, session.self_closing_stream = data:gsub("^(<stream:stream.*)/>$", "%1>");
- session.self_closing_stream = (session.self_closing_stream == 1)
-
- data = filter("bytes/in", data);
- if data then
- local ok, err = stream:feed(data);
- if ok then return; end
- 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 data;
end
- return true;
+ return "";
end
+ conn:setlistener(c2s_listener);
+ c2s_listener.onconnect(conn);
+
local frameBuffer = "";
- function session.data(data)
+ add_filter(sessions[conn], "bytes/in", function(data)
+ local cache = "";
frameBuffer = frameBuffer .. data;
local frame, length = parse_frame(frameBuffer);
while frame do
frameBuffer = frameBuffer:sub(length + 1);
- if not handle_frame(frame) then return; end
+ local result = handle_frame(frame);
+ if not result then return; end
+ cache = cache .. result;
frame, length = parse_frame(frameBuffer);
end
- end
-
- function session.send(s)
- conn:write(build_frame({ FIN = true, opcode = 0x01, data = tostring(s)}));
- end
-
- if c2s_timeout then
- add_task(c2s_timeout, function ()
- if session.type == "c2s_unauthed" then
- session:close("connection-timeout");
- end
- end);
- end
-
- session.dispatch_stanza = stream_callbacks.handlestanza;
-end
+ return cache;
-function listener.onincoming(conn, data)
- local session = sessions[conn];
- if session then
- session.data(data);
- else
- listener.onconnect(conn, data);
- session = sessions[conn];
- session.data(data);
- end
-end
-
-function listener.ondisconnect(conn, err)
- local session = sessions[conn];
- if session then
- (session.log or log)("info", "Client disconnected: %s", err or "connection closed");
- sm_destroy_session(session, err);
- sessions[conn] = nil;
- end
-end
-
-function listener.associate_session(conn, session)
- sessions[conn] = session;
-end
-
-function handle_request(event, path)
- local request, response = event.request, event.response;
-
- if not request.headers.sec_websocket_key then
- response.headers.content_type = "text/html";
- return [[<!DOCTYPE html><html><head><title>Websocket</title></head><body>
- <p>It works! Now point your WebSocket client to this URL to connect to Prosody.</p>
- </body></html>]];
- end
-
- local wants_xmpp = false;
- (request.headers.sec_websocket_protocol or ""):gsub("([^,]*),?", function (proto)
- if proto == "xmpp" then wants_xmpp = true; end
end);
- if not wants_xmpp then
- return 501;
- end
+ add_filter(sessions[conn], "bytes/out", function(data)
+ return build_frame({ FIN = true, opcode = 0x01, data = tostring(data)});
+ end);
- response.conn:setlistener(listener);
response.status = "101 Switching Protocols";
- response.headers.Upgrade = "websocket";
- response.headers.Connection = "Upgrade";
- response.headers.Sec_WebSocket_Accept = base64(sha1(request.headers.sec_websocket_key .. "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"));
- response.headers.Sec_WebSocket_Protocol = "xmpp";
- response.headers.Access_Control_Allow_Origin = cross_domain;
+ response.headers.upgrade = "websocket";
+ response.headers.connection = "Upgrade";
+ response.headers.sec_webSocket_accept = base64(sha1(request.headers.sec_websocket_key .. "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"));
+ response.headers.sec_webSocket_protocol = "xmpp";
+ response.headers.access_control_allow_origin = cross_domain;
return "";
end
@@ -481,7 +246,8 @@
function module.add_host(module)
module:depends("http");
module:provides("http", {
- name = "xmpp-websocket";
+ name = "websocket";
+ default_path = "xmpp-websocket";
route = {
["GET"] = handle_request;
["GET /"] = handle_request;