# See http://tools.ietf.org/html/draft-ietf-xmpp-3920bis#section-13.7.1.2 for more info.
DNS.0 = example.com
--otherName.0 = xmppAddr;FORMAT:UTF8,UTF8:example.com
++otherName.0 = xmppAddr;UTF8:example.com
otherName.1 = SRVName;IA5STRING:_xmpp-client.example.com
otherName.2 = SRVName;IA5STRING:_xmpp-server.example.com
DNS.1 = conference.example.com
--otherName.3 = xmppAddr;FORMAT:UTF8,UTF8:conference.example.com
++otherName.3 = xmppAddr;UTF8:conference.example.com
otherName.4 = SRVName;IA5STRING:_xmpp-server.conference.example.com
module "certmanager"
--- These are the defaults if not overridden in the config
-local default_ssl_ctx = { mode = "client", protocol = "sslv23", capath = "/etc/ssl/certs", verify = "none", options = "no_sslv2"; };
-local default_ssl_ctx_in = { mode = "server", protocol = "sslv23", capath = "/etc/ssl/certs", verify = "none", options = "no_sslv2"; };
-
-local default_ssl_ctx_mt = { __index = default_ssl_ctx };
-local default_ssl_ctx_in_mt = { __index = default_ssl_ctx_in };
-
-- Global SSL options if not overridden per-host
local default_ssl_config = configmanager.get("*", "core", "ssl");
- local default_verify = (ssl and ssl.x509 and { "peer", "client_once", "continue", "ignore_purpose" }) or "none";
- local default_options = { "no_sslv2" };
+local default_capath = "/etc/ssl/certs";
-function create_context(host, mode, config)
- local ssl_config = config and config.core.ssl or default_ssl_config;
- if ssl and ssl_config then
- local ctx, err = ssl_newcontext(setmetatable(ssl_config, mode == "client" and default_ssl_ctx_mt or default_ssl_ctx_in_mt));
- if not ctx then
- err = err or "invalid ssl config"
- local file = err:match("^error loading (.-) %(");
- if file then
- if file == "private key" then
- file = ssl_config.key or "your private key";
- elseif file == "certificate" then
- file = ssl_config.certificate or "your certificate file";
- end
- local reason = err:match("%((.+)%)$") or "some reason";
- if reason == "Permission denied" then
- reason = "Check that the permissions allow Prosody to read this file.";
- elseif reason == "No such file or directory" then
- reason = "Check that the path is correct, and the file exists.";
- elseif reason == "system lib" then
- reason = "Previous error (see logs), or other system error.";
- elseif reason == "(null)" or not reason then
- reason = "Check that the file exists and the permissions are correct";
- else
- reason = "Reason: "..tostring(reason):lower();
- end
- log("error", "SSL/TLS: Failed to load %s: %s", file, reason);
+function create_context(host, mode, user_ssl_config)
+ user_ssl_config = user_ssl_config or default_ssl_config;
+
+ if not ssl then return nil, "LuaSec (required for encryption) was not found"; end
+ if not user_ssl_config then return nil, "No SSL/TLS configuration present for "..host; end
+
+ local ssl_config = {
+ mode = mode;
+ protocol = user_ssl_config.protocol or "sslv23";
+ key = resolve_path(config_path, user_ssl_config.key);
+ password = user_ssl_config.password;
+ certificate = resolve_path(config_path, user_ssl_config.certificate);
+ capath = resolve_path(config_path, user_ssl_config.capath or default_capath);
+ cafile = resolve_path(config_path, user_ssl_config.cafile);
- verify = user_ssl_config.verify or default_verify;
- options = user_ssl_config.options or default_options;
++ verify = user_ssl_config.verify or "none";
++ options = user_ssl_config.options or "no_sslv2";
+ ciphers = user_ssl_config.ciphers;
+ depth = user_ssl_config.depth;
+ };
+
+ local ctx, err = ssl_newcontext(ssl_config);
+ if not ctx then
+ err = err or "invalid ssl config"
+ local file = err:match("^error loading (.-) %(");
+ if file then
+ if file == "private key" then
+ file = ssl_config.key or "your private key";
+ elseif file == "certificate" then
+ file = ssl_config.certificate or "your certificate file";
+ end
+ local reason = err:match("%((.+)%)$") or "some reason";
+ if reason == "Permission denied" then
+ reason = "Check that the permissions allow Prosody to read this file.";
+ elseif reason == "No such file or directory" then
+ reason = "Check that the path is correct, and the file exists.";
+ elseif reason == "system lib" then
+ reason = "Previous error (see logs), or other system error.";
+ elseif reason == "(null)" or not reason then
+ reason = "Check that the file exists and the permissions are correct";
else
- log("error", "SSL/TLS: Error initialising for host %s: %s", host, err );
+ reason = "Reason: "..tostring(reason):lower();
end
- end
- return ctx, err;
- elseif not ssl then
- return nil, "LuaSec (required for encryption) was not found";
+ log("error", "SSL/TLS: Failed to load %s: %s", file, reason);
+ else
+ log("error", "SSL/TLS: Error initialising for host %s: %s", host, err );
+ end
end
- return nil, "No SSL/TLS configuration present for "..host;
+ return ctx, err;
end
function reload_ssl_config()
local st = require "stanza";
local stanza = st.stanza;
local nameprep = require "util.encodings".stringprep.nameprep;
- local cert_verify_identity = require "util.x509".verify_identity;
-local fire_event = require "core.eventmanager".fire_event;
+local fire_event = prosody.events.fire_event;
local uuid_gen = require "util.uuid".generate;
local logger_init = require "util.logger".init;
session.streamid = uuid_gen();
(session.log or log)("debug", "incoming s2s received <stream:stream>");
- if session.to_host and not hosts[session.to_host] then
- -- Attempting to connect to a host we don't serve
- session:close({ condition = "host-unknown"; text = "This host does not serve "..session.to_host });
- return;
+ if session.to_host then
+ if not hosts[session.to_host] then
+ -- Attempting to connect to a host we don't serve
+ session:close({
+ condition = "host-unknown";
+ text = "This host does not serve "..session.to_host
+ });
+ return;
+ elseif hosts[session.to_host].disallow_s2s then
+ -- Attempting to connect to a host that disallows s2s
+ session:close({
+ condition = "policy-violation";
+ text = "Server-to-server communication is not allowed to this host";
+ });
+ return;
+ end
end
-
- if session.secure and not session.cert_chain_status then check_cert_status(session); end
-
send("<?xml version='1.0'?>");
send(stanza("stream:stream", { xmlns='jabber:server', ["xmlns:db"]='jabber:server:dialback',
["xmlns:stream"]='http://etherx.jabber.org/streams', id=session.streamid, from=session.to_host, to=session.from_host, version=(session.version > 0 and "1.0" or nil) }):top_tag());
function destroy_session(session, reason)
if session.destroyed then return; end
- (session.log or log)("debug", "Destroying "..tostring(session.direction).." session "..tostring(session.from_host).."->"..tostring(session.to_host)..(reason and (": "..reason) or ""));
- (session.log or log)("info", "Destroying "..tostring(session.direction).." session "..tostring(session.from_host).."->"..tostring(session.to_host));
++ (session.log or log)("debug", "Destroying "..tostring(session.direction).." session "..tostring(session.from_host).."->"..tostring(session.to_host));
if session.direction == "outgoing" then
hosts[session.from_host].s2sout[session.to_host] = nil;
rr.srv.target = self:name();
end
-
-function SRV_tostring(rr) -- - - - - - - - - - - - - - - - - - SRV_tostring
- local s = rr.srv;
- return string.format( '%5d %5d %5d %s', s.priority, s.weight, s.port, s.target );
+function resolver:PTR(rr)
+ rr.ptr = self:name();
end
-
function resolver:TXT(rr) -- - - - - - - - - - - - - - - - - - - - - - TXT
- rr.txt = self:sub (self:byte());
+ rr.txt = self:sub (rr.rdlength);
end
require "util.iterators";
local jid_bare = require "util.jid".bare;
local set, array = require "util.set", require "util.array";
--local cert_verify_identity = require "util.x509".verify_identity;
local commands = {};
local def_env = {};
for remotehost, session in pairs(host_session.s2sout) do
if (not match_jid) or remotehost:match(match_jid) or host:match(match_jid) then
count_out = count_out + 1;
-- print(" "..host.." -> "..remotehost..(session.cert_identity_status == "valid" and " (secure)" or "")..(session.secure and " (encrypted)" or "")..(session.compressed and " (compressed)" or ""));
++ print(" "..host.." -> "..remotehost..(session.secure and " (encrypted)" or "")..(session.compressed and " (compressed)" or ""));
if session.sendq then
print(" There are "..#session.sendq.." queued outgoing stanzas for this connection");
end
-- Pft! is what I say to list comprehensions
or (session.hosts and #array.collect(keys(session.hosts)):filter(subhost_filter)>0)) then
count_in = count_in + 1;
-- print(" "..host.." <- "..(session.from_host or "(unknown)")..(session.cert_identity_status == "valid" and " (secure)" or "")..(session.secure and " (encrypted)" or "")..(session.compressed and " (compressed)" or ""));
++ print(" "..host.." <- "..(session.from_host or "(unknown)")..(session.secure and " (encrypted)" or "")..(session.compressed and " (compressed)" or ""));
if session.type == "s2sin_unauthed" then
print(" Connection not yet authenticated");
end
return true, "Total: "..count_out.." outgoing, "..count_in.." incoming connections";
end
--local function print_subject(print, subject)
-- for _, entry in ipairs(subject) do
-- print(
-- (" %s: %q"):format(
-- entry.name or entry.oid,
-- entry.value:gsub("[\r\n%z%c]", " ")
-- )
-- );
-- end
--end
--
--function def_env.s2s:showcert(domain)
-- local ser = require "util.serialization".serialize;
-- local print = self.session.print;
-- local domain_sessions = set.new(array.collect(keys(incoming_s2s)))
-- /function(session) return session.from_host == domain; end;
-- for local_host in values(prosody.hosts) do
-- local s2sout = local_host.s2sout;
-- if s2sout and s2sout[domain] then
-- domain_sessions:add(s2sout[domain]);
-- end
-- end
-- local cert_set = {};
-- for session in domain_sessions do
-- local conn = session.conn;
-- conn = conn and conn:socket();
-- if not conn.getpeercertificate then
-- if conn.dohandshake then
-- error("This version of LuaSec does not support certificate viewing");
-- end
-- else
-- local cert = conn:getpeercertificate();
-- if cert then
-- local digest = cert:digest("sha1");
-- if not cert_set[digest] then
-- local chain_valid, chain_err = conn:getpeerchainvalid();
-- cert_set[digest] = {
-- {
-- from = session.from_host,
-- to = session.to_host,
-- direction = session.direction
-- };
-- chain_valid = chain_valid;
-- chain_err = chain_err;
-- cert = cert;
-- };
-- else
-- table.insert(cert_set[digest], {
-- from = session.from_host,
-- to = session.to_host,
-- direction = session.direction
-- });
-- end
-- end
-- end
-- end
-- local domain_certs = array.collect(values(cert_set));
-- -- Phew. We now have a array of unique certificates presented by domain.
-- local print = self.session.print;
-- local n_certs = #domain_certs;
--
-- if n_certs == 0 then
-- return "No certificates found for "..domain;
-- end
--
-- local function _capitalize_and_colon(byte)
-- return string.upper(byte)..":";
-- end
-- local function pretty_fingerprint(hash)
-- return hash:gsub("..", _capitalize_and_colon):sub(1, -2);
-- end
--
-- for cert_info in values(domain_certs) do
-- local cert = cert_info.cert;
-- print("---")
-- print("Fingerprint (SHA1): "..pretty_fingerprint(cert:digest("sha1")));
-- print("");
-- local n_streams = #cert_info;
-- print("Currently used on "..n_streams.." stream"..(n_streams==1 and "" or "s")..":");
-- for _, stream in ipairs(cert_info) do
-- if stream.direction == "incoming" then
-- print(" "..stream.to.." <- "..stream.from);
-- else
-- print(" "..stream.from.." -> "..stream.to);
-- end
-- end
-- print("");
-- local chain_valid, err = cert_info.chain_valid, cert_info.chain_err;
-- local valid_identity = cert_verify_identity(domain, "xmpp-server", cert);
-- print("Trusted certificate: "..(chain_valid and "Yes" or ("No ("..err..")")));
-- print("Issuer: ");
-- print_subject(print, cert:issuer());
-- print("");
-- print("Valid for "..domain..": "..(valid_identity and "Yes" or "No"));
-- print("Subject:");
-- print_subject(print, cert:subject());
-- end
-- print("---");
-- return ("Showing "..n_certs.." certificate"
-- ..(n_certs==1 and "" or "s")
-- .." presented by "..domain..".");
--end
--
function def_env.s2s:close(from, to)
local print, count = self.session.print, 0;
request.reply_before = os_time() + session.bosh_wait;
waiting_requests[request] = true;
end
- if inactive_sessions[session] then
- -- Session was marked as inactive, since we have
- -- a request open now, unmark it
- inactive_sessions[session] = nil;
- end
end
- if session.bosh_terminate then
- session.log("debug", "Closing session with %d requests open", #session.requests);
- session:close();
- return nil;
- else
- return true; -- Inform httpserver we shall reply later
- end
+ return true; -- Inform httpserver we shall reply later
end
end
local function bosh_reset_stream(session) session.notopen = true; end
+local stream_xmlns_attr = { xmlns = "urn:ietf:params:xml:ns:xmpp-streams" };
+
local function bosh_close_stream(session, reason)
(session.log or log)("info", "BOSH client disconnected");
- session_close_reply.attr.condition = reason;
+
+ local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate",
+ ["xmlns:streams"] = xmlns_streams });
+
+
+ if reason then
+ close_reply.attr.condition = "remote-stream-error";
+ if type(reason) == "string" then -- assume stream error
+ close_reply:tag("stream:error")
+ :tag(reason, {xmlns = xmlns_xmpp_streams});
+ elseif type(reason) == "table" then
+ if reason.condition then
+ close_reply:tag("stream:error")
+ :tag(reason.condition, stream_xmlns_attr):up();
+ if reason.text then
+ close_reply:tag("text", stream_xmlns_attr):text(reason.text):up();
+ end
+ if reason.extra then
+ close_reply:add_child(reason.extra);
+ end
+ elseif reason.name then -- a stanza
+ close_reply = reason;
+ end
+ end
+ log("info", "Disconnecting client, <stream:error> is: %s", tostring(close_reply));
+ end
+
+ local session_close_response = { headers = default_headers, body = tostring(close_reply) };
+
++ --FIXME: Quite sure we shouldn't reply to all requests with the error
for _, held_request in ipairs(session.requests) do
- held_request:send(session_close_reply);
+ held_request:send(session_close_response);
held_request:destroy();
end
sessions[session.sid] = nil;
end
end);
--module:hook_stanza("urn:ietf:params:xml:ns:xmpp-sasl", "failure", function (origin, stanza)
-- if origin.external_auth == "failed" then
-- module:log("debug", "SASL EXTERNAL failed, falling back to dialback");
-- s2s_initiate_dialback(origin);
-- return true;
-- end
--end, 100);
--
module:hook_stanza(xmlns_stream, "features", function (origin, stanza)
-- if not origin.external_auth or origin.external_auth == "failed" then
-- s2s_initiate_dialback(origin);
-- return true;
-- end
++ s2s_initiate_dialback(origin);
++ return true;
end, 100);
-- Offer dialback to incoming hosts
module:hook("s2s-stream-features", function (data)
-- data.features:tag("dialback", { xmlns='urn:xmpp:features:dialback' }):up();
++ data.features:tag("dialback", { xmlns='urn:xmpp:features:dialback' }):tag("optional"):up():up();
end);
local usermanager_user_exists = require "core.usermanager".user_exists;
local usermanager_create_user = require "core.usermanager".create_user;
local usermanager_set_password = require "core.usermanager".set_password;
-local datamanager_store = require "util.datamanager".store;
+local usermanager_delete_user = require "core.usermanager".delete_user;
local os_time = os.time;
local nodeprep = require "util.encodings".stringprep.nodeprep;
- local allow_registration = module:get_option_boolean("allow_registration", false);
+local jid_bare = require "util.jid".bare;
+
+local compat = module:get_option_boolean("registration_compat", true);
module:add_feature("jabber:iq:register");
- local register_stream_feature = st.stanza("register", {xmlns="http://jabber.org/features/iq-register"}):up();
- module:hook("stream-features", function(event)
- local session, features = event.origin, event.features;
-
- -- Advertise registration to unauthorized clients only.
- if not(allow_registration) or session.type ~= "c2s_unauthed" then
- return
- end
-
- features:add_child(register_stream_feature);
- end);
-
-module:add_iq_handler("c2s", "jabber:iq:register", function (session, stanza)
- if stanza.tags[1].name == "query" then
- local query = stanza.tags[1];
- if stanza.attr.type == "get" then
- local reply = st.reply(stanza);
- reply:tag("query", {xmlns = "jabber:iq:register"})
- :tag("registered"):up()
- :tag("username"):text(session.username):up()
- :tag("password"):up();
- session.send(reply);
- elseif stanza.attr.type == "set" then
- if query.tags[1] and query.tags[1].name == "remove" then
- -- TODO delete user auth data, send iq response, kick all user resources with a <not-authorized/>, delete all user data
- local username, host = session.username, session.host;
- --session.send(st.error_reply(stanza, "cancel", "not-allowed"));
- --return;
- --usermanager_set_password(username, host, nil); -- Disable account
- -- FIXME the disabling currently allows a different user to recreate the account
- -- we should add an in-memory account block mode when we have threading
- session.send(st.reply(stanza));
- local roster = session.roster;
- for _, session in pairs(hosts[host].sessions[username].sessions) do -- disconnect all resources
- session:close({condition = "not-authorized", text = "Account deleted"});
- end
- -- TODO datamanager should be able to delete all user data itself
- datamanager.store(username, host, "vcard", nil);
- datamanager.store(username, host, "private", nil);
- datamanager.list_store(username, host, "offline", nil);
- local bare = username.."@"..host;
- for jid, item in pairs(roster) do
- if jid and jid ~= "pending" then
- if item.subscription == "both" or item.subscription == "from" or (roster.pending and roster.pending[jid]) then
- core_post_stanza(hosts[host], st.presence({type="unsubscribed", from=bare, to=jid}));
- end
- if item.subscription == "both" or item.subscription == "to" or item.ask then
- core_post_stanza(hosts[host], st.presence({type="unsubscribe", from=bare, to=jid}));
- end
+local function handle_registration_stanza(event)
+ local session, stanza = event.origin, event.stanza;
+
+ local query = stanza.tags[1];
+ if stanza.attr.type == "get" then
+ local reply = st.reply(stanza);
+ reply:tag("query", {xmlns = "jabber:iq:register"})
+ :tag("registered"):up()
+ :tag("username"):text(session.username):up()
+ :tag("password"):up();
+ session.send(reply);
+ else -- stanza.attr.type == "set"
+ if query.tags[1] and query.tags[1].name == "remove" then
+ -- TODO delete user auth data, send iq response, kick all user resources with a <not-authorized/>, delete all user data
+ local username, host = session.username, session.host;
+
+ local ok, err = usermanager_delete_user(username, host);
+
+ if not ok then
+ module:log("debug", "Removing user account %s@%s failed: %s", username, host, err);
+ session.send(st.error_reply(stanza, "cancel", "service-unavailable", err));
+ return true;
+ end
+
+ session.send(st.reply(stanza));
+ local roster = session.roster;
+ for _, session in pairs(hosts[host].sessions[username].sessions) do -- disconnect all resources
+ session:close({condition = "not-authorized", text = "Account deleted"});
+ end
+ -- TODO datamanager should be able to delete all user data itself
+ datamanager.store(username, host, "vcard", nil);
+ datamanager.store(username, host, "private", nil);
+ datamanager.list_store(username, host, "offline", nil);
+ local bare = username.."@"..host;
+ for jid, item in pairs(roster) do
+ if jid and jid ~= "pending" then
+ if item.subscription == "both" or item.subscription == "from" or (roster.pending and roster.pending[jid]) then
+ core_post_stanza(hosts[host], st.presence({type="unsubscribed", from=bare, to=jid}));
+ end
+ if item.subscription == "both" or item.subscription == "to" or item.ask then
+ core_post_stanza(hosts[host], st.presence({type="unsubscribe", from=bare, to=jid}));
end
end
- datamanager.store(username, host, "roster", nil);
- datamanager.store(username, host, "privacy", nil);
- datamanager.store(username, host, "accounts", nil); -- delete accounts datastore at the end
- module:log("info", "User removed their account: %s@%s", username, host);
- module:fire_event("user-deregistered", { username = username, host = host, source = "mod_register", session = session });
- else
- local username = query:child_with_name("username");
- local password = query:child_with_name("password");
- if username and password then
- -- FIXME shouldn't use table.concat
- username = nodeprep(table.concat(username));
- password = table.concat(password);
- if username == session.username then
- if usermanager_set_password(username, session.host, password) then
- session.send(st.reply(stanza));
- else
- -- TODO unable to write file, file may be locked, etc, what's the correct error?
- session.send(st.error_reply(stanza, "wait", "internal-server-error"));
- end
+ end
+ datamanager.store(username, host, "roster", nil);
+ datamanager.store(username, host, "privacy", nil);
+ module:log("info", "User removed their account: %s@%s", username, host);
+ module:fire_event("user-deregistered", { username = username, host = host, source = "mod_register", session = session });
+ else
+ local username = nodeprep(query:get_child("username"):get_text());
+ local password = query:get_child("password"):get_text();
+ if username and password then
+ if username == session.username then
+ if usermanager_set_password(username, password, session.host) then
+ session.send(st.reply(stanza));
else
- session.send(st.error_reply(stanza, "modify", "bad-request"));
+ -- TODO unable to write file, file may be locked, etc, what's the correct error?
+ session.send(st.error_reply(stanza, "wait", "internal-server-error"));
end
else
session.send(st.error_reply(stanza, "modify", "bad-request"));
for _, ip in ipairs(whitelisted_ips) do whitelisted_ips[ip] = true; end
for _, ip in ipairs(blacklisted_ips) do blacklisted_ips[ip] = true; end
-module:add_iq_handler("c2s_unauthed", "jabber:iq:register", function (session, stanza)
- if module:get_option("allow_registration") == false then
+module:hook("stanza/iq/jabber:iq:register:query", function(event)
+ local session, stanza = event.origin, event.stanza;
+
- if not(allow_registration) or session.type ~= "c2s_unauthed" then
++ if module:get_option("allow_registration") == false or session.type ~= "c2s_unauthed" then
session.send(st.error_reply(stanza, "cancel", "service-unavailable"));
- elseif stanza.tags[1].name == "query" then
+ else
local query = stanza.tags[1];
if stanza.attr.type == "get" then
local reply = st.reply(stanza);
local st = require "util.stanza";
local sm_bind_resource = require "core.sessionmanager".bind_resource;
local sm_make_authenticated = require "core.sessionmanager".make_authenticated;
- local s2s_make_authenticated = require "core.s2smanager".make_authenticated;
local base64 = require "util.encodings".base64;
- local cert_verify_identity = require "util.x509".verify_identity;
-
local nodeprep = require "util.encodings".stringprep.nodeprep;
-local datamanager_load = require "util.datamanager".load;
-local usermanager_validate_credentials = require "core.usermanager".validate_credentials;
-local usermanager_get_supported_methods = require "core.usermanager".get_supported_methods;
-local usermanager_user_exists = require "core.usermanager".user_exists;
-local usermanager_get_password = require "core.usermanager".get_password;
-local t_concat, t_insert = table.concat, table.insert;
+local usermanager_get_sasl_handler = require "core.usermanager".get_sasl_handler;
local tostring = tostring;
-local jid_split = require "util.jid".split;
-local md5 = require "util.hashes".md5;
-local config = require "core.configmanager";
local secure_auth_only = module:get_option("c2s_require_encryption") or module:get_option("require_encryption");
-local sasl_backend = module:get_option("sasl_backend") or "builtin";
-
--- Cyrus config options
-local require_provisioning = module:get_option("cyrus_require_provisioning") or false;
-local cyrus_service_realm = module:get_option("cyrus_service_realm");
-local cyrus_service_name = module:get_option("cyrus_service_name");
-local cyrus_application_name = module:get_option("cyrus_application_name");
+local allow_unencrypted_plain_auth = module:get_option("allow_unencrypted_plain_auth")
local log = module._log;
local s = build_reply(status, ret, err_msg);
log("debug", "sasl reply: %s", tostring(s));
session.send(s);
+ return true;
end
- module:hook_stanza(xmlns_sasl, "success", function (session, stanza)
- if session.type ~= "s2sout_unauthed" or session.external_auth ~= "attempting" then return; end
- module:log("debug", "SASL EXTERNAL with %s succeeded", session.to_host);
- session.external_auth = "succeeded"
- session:reset_stream();
-
- local default_stream_attr = {xmlns = "jabber:server", ["xmlns:stream"] = "http://etherx.jabber.org/streams",
- ["xmlns:db"] = 'jabber:server:dialback', version = "1.0", to = session.to_host, from = session.from_host};
- session.sends2s("<?xml version='1.0'?>");
- session.sends2s(st.stanza("stream:stream", default_stream_attr):top_tag());
-
- s2s_make_authenticated(session, session.to_host);
- return true;
- end)
-
- module:hook_stanza(xmlns_sasl, "failure", function (session, stanza)
- if session.type ~= "s2sout_unauthed" or session.external_auth ~= "attempting" then return; end
-
- module:log("info", "SASL EXTERNAL with %s failed", session.to_host)
- -- TODO: Log the failure reason
- session.external_auth = "failed"
- end, 500)
-
- module:hook_stanza(xmlns_sasl, "failure", function (session, stanza)
- -- TODO: Dialback wasn't loaded. Do something useful.
- end, 90)
-
- module:hook_stanza("http://etherx.jabber.org/streams", "features", function (session, stanza)
- if session.type ~= "s2sout_unauthed" or not session.secure then return; end
-
- local mechanisms = stanza:get_child("mechanisms", xmlns_sasl)
- if mechanisms then
- for mech in mechanisms:childtags() do
- if mech[1] == "EXTERNAL" then
- module:log("debug", "Initiating SASL EXTERNAL with %s", session.to_host);
- local reply = st.stanza("auth", {xmlns = xmlns_sasl, mechanism = "EXTERNAL"});
- reply:text(base64.encode(session.from_host))
- session.sends2s(reply)
- session.external_auth = "attempting"
- return true
- end
- end
- end
- end, 150);
-
- local function s2s_external_auth(session, stanza)
- local mechanism = stanza.attr.mechanism;
-
- if not session.secure then
- if mechanism == "EXTERNAL" then
- session.sends2s(build_reply("failure", "encryption-required"))
- else
- session.sends2s(build_reply("failure", "invalid-mechanism"))
- end
- return true;
- end
-
- if mechanism ~= "EXTERNAL" or session.cert_chain_status ~= "valid" then
- session.sends2s(build_reply("failure", "invalid-mechanism"))
- return true;
- end
-
- local text = stanza[1]
- if not text then
- session.sends2s(build_reply("failure", "malformed-request"))
- return true
- end
-
- -- Either the value is "=" and we've already verified the external
- -- cert identity, or the value is a string and either matches the
- -- from_host (
-
- text = base64.decode(text)
- if not text then
- session.sends2s(build_reply("failure", "incorrect-encoding"))
- return true;
- end
-
- if session.cert_identity_status == "valid" then
- if text ~= "" and text ~= session.from_host then
- session.sends2s(build_reply("failure", "invalid-authzid"))
- return true
- end
- else
- if text == "" then
- session.sends2s(build_reply("failure", "invalid-authzid"))
- return true
- end
-
- local cert = session.conn:socket():getpeercertificate()
- if (cert_verify_identity(text, "xmpp-server", cert)) then
- session.cert_identity_status = "valid"
- else
- session.cert_identity_status = "invalid"
- session.sends2s(build_reply("failure", "invalid-authzid"))
- return true
- end
- end
-
- session.external_auth = "succeeded"
-
- if not session.from_host then
- session.from_host = text;
- end
- session.sends2s(build_reply("success"))
- module:log("info", "Accepting SASL EXTERNAL identity from %s", text or session.from_host);
- s2s_make_authenticated(session, text or session.from_host)
- session:reset_stream();
- return true
- end
-
-module:add_handler("c2s_unauthed", "auth", xmlns_sasl, sasl_handler);
-module:add_handler("c2s_unauthed", "abort", xmlns_sasl, sasl_handler);
-module:add_handler("c2s_unauthed", "response", xmlns_sasl, sasl_handler);
+module:hook("stanza/urn:ietf:params:xml:ns:xmpp-sasl:auth", function(event)
+ local session, stanza = event.origin, event.stanza;
- if session.type == "s2sin_unauthed" then
- return s2s_external_auth(session, stanza)
- end
-
+ if session.type ~= "c2s_unauthed" then return; end
+
+ if session.sasl_handler and session.sasl_handler.selected then
+ session.sasl_handler = nil; -- allow starting a new SASL negotiation before completing an old one
+ end
+ if not session.sasl_handler then
+ session.sasl_handler = usermanager_get_sasl_handler(module.host);
+ end
+ local mechanism = stanza.attr.mechanism;
+ if not session.secure and (secure_auth_only or (mechanism == "PLAIN" and not allow_unencrypted_plain_auth)) then
+ session.send(build_reply("failure", "encryption-required"));
+ return true;
+ end
+ local valid_mechanism = session.sasl_handler:select(mechanism);
+ if not valid_mechanism then
+ session.send(build_reply("failure", "invalid-mechanism"));
+ return true;
+ end
+ return sasl_process_cdata(session, stanza);
+end);
+module:hook("stanza/urn:ietf:params:xml:ns:xmpp-sasl:response", function(event)
+ local session = event.origin;
+ if not(session.sasl_handler and session.sasl_handler.selected) then
+ session.send(build_reply("failure", "not-authorized", "Out of order SASL element"));
+ return true;
+ end
+ return sasl_process_cdata(session, event.stanza);
+end);
+module:hook("stanza/urn:ietf:params:xml:ns:xmpp-sasl:abort", function(event)
+ local session = event.origin;
+ session.sasl_handler = nil;
+ session.send(build_reply("failure", "aborted"));
+ return true;
+end);
local mechanisms_attr = { xmlns='urn:ietf:params:xml:ns:xmpp-sasl' };
local bind_attr = { xmlns='urn:ietf:params:xml:ns:xmpp-bind' };
end
end);
- module:hook("s2s-stream-features", function(event)
- local origin, features = event.origin, event.features;
- if origin.secure and origin.type == "s2sin_unauthed" then
- -- Offer EXTERNAL if chain is valid and either we didn't validate
- -- the identity or it passed.
- if origin.cert_chain_status == "valid" and origin.cert_identity_status ~= "invalid" then --TODO: Configurable
- module:log("debug", "Offering SASL EXTERNAL")
- features:tag("mechanisms", { xmlns = xmlns_sasl })
- :tag("mechanism"):text("EXTERNAL")
- :up():up();
- end
- end
- end);
-
-module:add_iq_handler("c2s", "urn:ietf:params:xml:ns:xmpp-bind", function(session, stanza)
- log("debug", "Client requesting a resource bind");
+module:hook("iq/self/urn:ietf:params:xml:ns:xmpp-bind:bind", function(event)
+ local origin, stanza = event.origin, event.stanza;
local resource;
if stanza.attr.type == "set" then
local bind = stanza.tags[1];
end
function room_mt:get_disco_info(stanza)
- local count = 0; for _ in pairs(self._occupants) do count = count + 1; end
return st.reply(stanza):query("http://jabber.org/protocol/disco#info")
- :tag("identity", {category="conference", type="text"}):up()
- :tag("feature", {var="http://jabber.org/protocol/muc"});
+ :tag("identity", {category="conference", type="text", name=self:get_name()}):up()
+ :tag("feature", {var="http://jabber.org/protocol/muc"}):up()
+ :tag("feature", {var=self:get_password() and "muc_passwordprotected" or "muc_unsecured"}):up()
+ :tag("feature", {var=self:is_moderated() and "muc_moderated" or "muc_unmoderated"}):up()
+ :tag("feature", {var=self:is_members_only() and "muc_membersonly" or "muc_open"}):up()
+ :tag("feature", {var=self:is_persistent() and "muc_persistent" or "muc_temporary"}):up()
+ :tag("feature", {var=self:is_hidden() and "muc_hidden" or "muc_public"}):up()
+ :tag("feature", {var=self._data.whois ~= "anyone" and "muc_semianonymous" or "muc_nonanonymous"}):up()
+ :add_child(dataform.new({
+ { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/muc#roominfo" },
- { name = "muc#roominfo_description", label = "Description"},
- { name = "muc#roominfo_occupants", label = "Number of occupants", value = tostring(count) }
++ { name = "muc#roominfo_description", label = "Description"}
+ }):form({["muc#roominfo_description"] = self:get_description()}, 'result'))
+ ;
end
function room_mt:get_disco_items(stanza)
local reply = st.reply(stanza):query("http://jabber.org/protocol/disco#items");
};
/***************** STRINGPREP *****************/
--#ifdef USE_STRINGPREP_ICU
++#ifndef USE_STRINGPREP_ICU
++/****************** libidn ********************/
++
++#include <stringprep.h>
++
++static int stringprep_prep(lua_State *L, const Stringprep_profile *profile)
++{
++ size_t len;
++ const char *s;
++ char string[1024];
++ int ret;
++ if(!lua_isstring(L, 1)) {
++ lua_pushnil(L);
++ return 1;
++ }
++ s = lua_tolstring(L, 1, &len);
++ if (len >= 1024) {
++ lua_pushnil(L);
++ return 1; /* TODO return error message */
++ }
++ strcpy(string, s);
++ ret = stringprep(string, 1024, (Stringprep_profile_flags)0, profile);
++ if (ret == STRINGPREP_OK) {
++ lua_pushstring(L, string);
++ return 1;
++ } else {
++ lua_pushnil(L);
++ return 1; /* TODO return error message */
++ }
++}
++
++#define MAKE_PREP_FUNC(myFunc, prep) \
++static int myFunc(lua_State *L) { return stringprep_prep(L, prep); }
++
++MAKE_PREP_FUNC(Lstringprep_nameprep, stringprep_nameprep) /** stringprep.nameprep(s) */
++MAKE_PREP_FUNC(Lstringprep_nodeprep, stringprep_xmpp_nodeprep) /** stringprep.nodeprep(s) */
++MAKE_PREP_FUNC(Lstringprep_resourceprep, stringprep_xmpp_resourceprep) /** stringprep.resourceprep(s) */
++MAKE_PREP_FUNC(Lstringprep_saslprep, stringprep_saslprep) /** stringprep.saslprep(s) */
++
++static const luaL_Reg Reg_stringprep[] =
++{
++ { "nameprep", Lstringprep_nameprep },
++ { "nodeprep", Lstringprep_nodeprep },
++ { "resourceprep", Lstringprep_resourceprep },
++ { "saslprep", Lstringprep_saslprep },
++ { NULL, NULL }
++};
++#else
#include <unicode/usprep.h>
#include <unicode/ustring.h>
#include <unicode/utrace.h>
return 1;
}
u_strFromUTF8(unprepped, 1024, &unprepped_len, input, input_len, &err);
-- if (U_FAILURE(err)) {
-- luah_pushnil(L);
-- return 1;
-- }
prepped_len = usprep_prepare(profile, unprepped, unprepped_len, prepped, 1024, 0, NULL, &err);
if (U_FAILURE(err)) {
lua_pushnil(L);
return 1;
} else {
u_strToUTF8(output, 1024, &output_len, prepped, prepped_len, &err);
-- if (U_SUCCESS(err) && output_len < 1024)
++ if(output_len < 1024)
lua_pushlstring(L, output, output_len);
else
lua_pushnil(L);
{ "saslprep", Lstringprep_saslprep },
{ NULL, NULL }
};
--#else /* USE_STRINGPREP_ICU */
++#endif
++/***************** IDNA *****************/
++#ifndef USE_STRINGPREP_ICU
/****************** libidn ********************/
--#include <stringprep.h>
++#include <idna.h>
++#include <idn-free.h>
--static int stringprep_prep(lua_State *L, const Stringprep_profile *profile)
++static int Lidna_to_ascii(lua_State *L) /** idna.to_ascii(s) */
{
size_t len;
-- const char *s;
-- char string[1024];
-- int ret;
-- if(!lua_isstring(L, 1)) {
-- lua_pushnil(L);
++ const char *s = luaL_checklstring(L, 1, &len);
++ char* output = NULL;
++ int ret = idna_to_ascii_8z(s, &output, IDNA_USE_STD3_ASCII_RULES);
++ if (ret == IDNA_SUCCESS) {
++ lua_pushstring(L, output);
++ idn_free(output);
return 1;
-- }
-- s = lua_tolstring(L, 1, &len);
-- if (len >= 1024) {
++ } else {
lua_pushnil(L);
++ idn_free(output);
return 1; /* TODO return error message */
}
-- strcpy(string, s);
-- ret = stringprep(string, 1024, (Stringprep_profile_flags)0, profile);
-- if (ret == STRINGPREP_OK) {
-- lua_pushstring(L, string);
++}
++
++static int Lidna_to_unicode(lua_State *L) /** idna.to_unicode(s) */
++{
++ size_t len;
++ const char *s = luaL_checklstring(L, 1, &len);
++ char* output = NULL;
++ int ret = idna_to_unicode_8z8z(s, &output, 0);
++ if (ret == IDNA_SUCCESS) {
++ lua_pushstring(L, output);
++ idn_free(output);
return 1;
} else {
lua_pushnil(L);
++ idn_free(output);
return 1; /* TODO return error message */
}
}
--
--#define MAKE_PREP_FUNC(myFunc, prep) \
--static int myFunc(lua_State *L) { return stringprep_prep(L, prep); }
--
--MAKE_PREP_FUNC(Lstringprep_nameprep, stringprep_nameprep) /** stringprep.nameprep(s) */
--MAKE_PREP_FUNC(Lstringprep_nodeprep, stringprep_xmpp_nodeprep) /** stringprep.nodeprep(s) */
--MAKE_PREP_FUNC(Lstringprep_resourceprep, stringprep_xmpp_resourceprep) /** stringprep.resourceprep(s) */
--MAKE_PREP_FUNC(Lstringprep_saslprep, stringprep_saslprep) /** stringprep.saslprep(s) */
--
--static const luaL_Reg Reg_stringprep[] =
--{
-- { "nameprep", Lstringprep_nameprep },
-- { "nodeprep", Lstringprep_nodeprep },
-- { "resourceprep", Lstringprep_resourceprep },
-- { "saslprep", Lstringprep_saslprep },
-- { NULL, NULL }
--};
--#endif
--
--/***************** IDNA *****************/
--#ifdef USE_STRINGPREP_ICU
++#else
#include <unicode/ustdio.h>
#include <unicode/uidna.h>
/* IDNA2003 or IDNA2008 ? ? ? */
char output[1024];
u_strFromUTF8(ustr, 1024, &ulen, s, len, &err);
-- if (U_FAILURE(err)) {
-- lua_pushnil(L);
-- return 1;
-- }
--
dest_len = uidna_IDNToASCII(ustr, ulen, dest, 1024, UIDNA_USE_STD3_RULES, NULL, &err);
if (U_FAILURE(err)) {
lua_pushnil(L);
return 1;
} else {
u_strToUTF8(output, 1024, &output_len, dest, dest_len, &err);
-- if (U_SUCCESS(err) && output_len < 1024)
++ if(output_len < 1024)
lua_pushlstring(L, output, output_len);
else
lua_pushnil(L);
size_t len;
int32_t ulen, dest_len, output_len;
const char *s = luaL_checklstring(L, 1, &len);
-- UChar ustr[1024];
++ UChar* ustr;
UErrorCode err = U_ZERO_ERROR;
UChar dest[1024];
char output[1024];
u_strFromUTF8(ustr, 1024, &ulen, s, len, &err);
-- if (U_FAILURE(err)) {
-- lua_pushnil(L);
-- return 1;
-- }
--
dest_len = uidna_IDNToUnicode(ustr, ulen, dest, 1024, UIDNA_USE_STD3_RULES, NULL, &err);
if (U_FAILURE(err)) {
lua_pushnil(L);
return 1;
} else {
u_strToUTF8(output, 1024, &output_len, dest, dest_len, &err);
-- if (U_SUCCESS(err) && output_len < 1024)
++ if(output_len < 1024)
lua_pushlstring(L, output, output_len);
else
lua_pushnil(L);
return 1;
}
}
--
--#else /* USE_STRINGPREP_ICU */
--/****************** libidn ********************/
--
--#include <idna.h>
--#include <idn-free.h>
--
--static int Lidna_to_ascii(lua_State *L) /** idna.to_ascii(s) */
--{
-- size_t len;
-- const char *s = luaL_checklstring(L, 1, &len);
-- char* output = NULL;
-- int ret = idna_to_ascii_8z(s, &output, IDNA_USE_STD3_ASCII_RULES);
-- if (ret == IDNA_SUCCESS) {
-- lua_pushstring(L, output);
-- idn_free(output);
-- return 1;
-- } else {
-- lua_pushnil(L);
-- idn_free(output);
-- return 1; /* TODO return error message */
-- }
--}
--
--static int Lidna_to_unicode(lua_State *L) /** idna.to_unicode(s) */
--{
-- size_t len;
-- const char *s = luaL_checklstring(L, 1, &len);
-- char* output = NULL;
-- int ret = idna_to_unicode_8z8z(s, &output, 0);
-- if (ret == IDNA_SUCCESS) {
-- lua_pushstring(L, output);
-- idn_free(output);
-- return 1;
-- } else {
-- lua_pushnil(L);
-- idn_free(output);
-- return 1; /* TODO return error message */
-- }
--}
#endif
static const luaL_Reg Reg_idna[] =
local lxp = require "lxp";
local st = require "util.stanza";
--local stanza_mt = st.stanza_mt;
local tostring = tostring;
local t_insert = table.insert;
local t_concat = table.concat;
--local t_remove = table.remove;
--local setmetatable = setmetatable;
local default_log = require "util.logger".init("xmppstream");
local stream_default_ns = stream_callbacks.default_ns;
-- local stack = {};
local chardata, stanza = {};
local non_streamns_depth = 0;
function xml_handlers:StartElement(tagname, attr)
if stanza and #chardata > 0 then
-- We have some character data in the buffer
-- t_insert(stanza, t_concat(chardata));
++ stanza:text(t_concat(chardata));
chardata = {};
end
local curr_ns,name = tagname:match(ns_pattern);
cb_error(session, "invalid-top-level-element");
end
-- stanza = setmetatable({ name = name, attr = attr, tags = {} }, stanza_mt);
++ stanza = st.stanza(name, attr);
else -- we are inside a stanza, so add a tag
-- t_insert(stack, stanza);
-- local oldstanza = stanza;
-- stanza = setmetatable({ name = name, attr = attr, tags = {} }, stanza_mt);
-- t_insert(oldstanza, stanza);
-- t_insert(oldstanza.tags, stanza);
++ stanza:tag(name, attr);
end
end
function xml_handlers:CharacterData(data)
if stanza then
if #chardata > 0 then
-- We have some character data in the buffer
-- t_insert(stanza, t_concat(chardata));
++ stanza:text(t_concat(chardata));
chardata = {};
end
-- Complete stanza
-- if #stack == 0 then
++ local last_add = stanza.last_add;
++ if not last_add or #last_add == 0 then
if tagname ~= stream_error_tag then
cb_handlestanza(session, stanza);
else
end
stanza = nil;
else
-- stanza = t_remove(stack);
++ stanza:up();
end
else
if tagname == stream_tag then
cb_error(session, "parse-error", "unexpected-element-close", name);
end
stanza, chardata = nil, {};
-- stack = {};
end
end
--
-- local function restricted_handler()
++
++ local function restricted_handler(parser)
cb_error(session, "parse-error", "restricted-xml", "Restricted XML, see RFC 6120 section 11.1.");
++ if not parser:stop() then
++ error("Failed to abort parsing");
++ end
end
if lxp_supports_doctype then
local function reset()
stanza, chardata = nil, {};
-- stack = {};
end
local function set_session(stream, new_session)