--- /dev/null
-otherName.0 = xmppAddr;FORMAT:UTF8,UTF8:example.com
+ oid_section = new_oids
+
+ [ new_oids ]
+
+ # RFC 3920 section 5.1.1 defines this OID
+ xmppAddr = 1.3.6.1.5.5.7.8.5
+
+ # RFC 4985 defines this OID
+ SRVName = 1.3.6.1.5.5.7.8.7
+
+ [ req ]
+
+ default_bits = 4096
+ default_keyfile = example.com.key
+ distinguished_name = distinguished_name
+ req_extensions = v3_extensions
+ x509_extensions = v3_extensions
+
+ # ask about the DN?
+ prompt = no
+
+ [ distinguished_name ]
+
+ commonName = example.com
+ countryName = GB
+ localityName = The Internet
+ organizationName = Your Organisation
+ organizationalUnitName = XMPP Department
+ emailAddress = xmpp@example.com
+
+ [ v3_extensions ]
+
+ # for certificate requests (req_extensions)
+ # and self-signed certificates (x509_extensions)
+
+ basicConstraints = CA:FALSE
+ keyUsage = digitalSignature,keyEncipherment
+ extendedKeyUsage = serverAuth,clientAuth
+ subjectAltName = @subject_alternative_name
+
+ [ subject_alternative_name ]
+
+ # See http://tools.ietf.org/html/draft-ietf-xmpp-3920bis#section-13.7.1.2 for more info.
+
+ DNS.0 = example.com
-otherName.3 = xmppAddr;FORMAT:UTF8,UTF8:conference.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;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)("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)..(reason and (": "..reason) or ""));
++ (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
--- /dev/null
-local cert_verify_identity = require "util.x509".verify_identity;
+ -- Prosody IM
+ -- Copyright (C) 2008-2010 Matthew Wild
+ -- Copyright (C) 2008-2010 Waqas Hussain
+ --
+ -- This project is MIT/X11 licensed. Please see the
+ -- COPYING file in the source package for more information.
+ --
+
+ module.host = "*";
+
+ local _G = _G;
+
+ local prosody = _G.prosody;
+ local hosts = prosody.hosts;
+ local connlisteners_register = require "net.connlisteners".register;
+
+ local console_listener = { default_port = 5582; default_mode = "*l"; default_interface = "127.0.0.1" };
+
+ require "util.iterators";
+ local jid_bare = require "util.jid".bare;
+ local set, array = require "util.set", require "util.array";
- print(" "..host.." -> "..remotehost..(session.cert_identity_status == "valid" and " (secure)" or "")..(session.secure and " (encrypted)" or "")..(session.compressed and " (compressed)" or ""));
+
+ local commands = {};
+ local def_env = {};
+ local default_env_mt = { __index = def_env };
+
+ prosody.console = { commands = commands, env = def_env };
+
+ local function redirect_output(_G, session)
+ local env = setmetatable({ print = session.print }, { __index = function (t, k) return rawget(_G, k); end });
+ env.dofile = function(name)
+ local f, err = loadfile(name);
+ if not f then return f, err; end
+ return setfenv(f, env)();
+ end;
+ return env;
+ end
+
+ console = {};
+
+ function console:new_session(conn)
+ local w = function(s) conn:write(s:gsub("\n", "\r\n")); end;
+ local session = { conn = conn;
+ send = function (t) w(tostring(t)); end;
+ print = function (...)
+ local t = {};
+ for i=1,select("#", ...) do
+ t[i] = tostring(select(i, ...));
+ end
+ w("| "..table.concat(t, "\t").."\n");
+ end;
+ disconnect = function () conn:close(); end;
+ };
+ session.env = setmetatable({}, default_env_mt);
+
+ -- Load up environment with helper objects
+ for name, t in pairs(def_env) do
+ if type(t) == "table" then
+ session.env[name] = setmetatable({ session = session }, { __index = t });
+ end
+ end
+
+ return session;
+ end
+
+ local sessions = {};
+
+ function console_listener.onconnect(conn)
+ -- Handle new connection
+ local session = console:new_session(conn);
+ sessions[conn] = session;
+ printbanner(session);
+ session.send(string.char(0));
+ end
+
+ function console_listener.onincoming(conn, data)
+ local session = sessions[conn];
+
+ -- Handle data
+ (function(session, data)
+ local useglobalenv;
+
+ if data:match("^>") then
+ data = data:gsub("^>", "");
+ useglobalenv = true;
+ elseif data == "\004" then
+ commands["bye"](session, data);
+ return;
+ else
+ local command = data:lower();
+ command = data:match("^%w+") or data:match("%p");
+ if commands[command] then
+ commands[command](session, data);
+ return;
+ end
+ end
+
+ session.env._ = data;
+
+ local chunkname = "=console";
+ local chunk, err = loadstring("return "..data, chunkname);
+ if not chunk then
+ chunk, err = loadstring(data, chunkname);
+ if not chunk then
+ err = err:gsub("^%[string .-%]:%d+: ", "");
+ err = err:gsub("^:%d+: ", "");
+ err = err:gsub("'<eof>'", "the end of the line");
+ session.print("Sorry, I couldn't understand that... "..err);
+ return;
+ end
+ end
+
+ setfenv(chunk, (useglobalenv and redirect_output(_G, session)) or session.env or nil);
+
+ local ranok, taskok, message = pcall(chunk);
+
+ if not (ranok or message or useglobalenv) and commands[data:lower()] then
+ commands[data:lower()](session, data);
+ return;
+ end
+
+ if not ranok then
+ session.print("Fatal error while running command, it did not complete");
+ session.print("Error: "..taskok);
+ return;
+ end
+
+ if not message then
+ session.print("Result: "..tostring(taskok));
+ return;
+ elseif (not taskok) and message then
+ session.print("Command completed with a problem");
+ session.print("Message: "..tostring(message));
+ return;
+ end
+
+ session.print("OK: "..tostring(message));
+ end)(session, data);
+
+ session.send(string.char(0));
+ end
+
+ function console_listener.ondisconnect(conn, err)
+ local session = sessions[conn];
+ if session then
+ session.disconnect();
+ sessions[conn] = nil;
+ end
+ end
+
+ connlisteners_register('console', console_listener);
+
+ -- Console commands --
+ -- These are simple commands, not valid standalone in Lua
+
+ function commands.bye(session)
+ session.print("See you! :)");
+ session.disconnect();
+ end
+ commands.quit, commands.exit = commands.bye, commands.bye;
+
+ commands["!"] = function (session, data)
+ if data:match("^!!") and session.env._ then
+ session.print("!> "..session.env._);
+ return console_listener.onincoming(session.conn, session.env._);
+ end
+ local old, new = data:match("^!(.-[^\\])!(.-)!$");
+ if old and new then
+ local ok, res = pcall(string.gsub, session.env._, old, new);
+ if not ok then
+ session.print(res)
+ return;
+ end
+ session.print("!> "..res);
+ return console_listener.onincoming(session.conn, res);
+ end
+ session.print("Sorry, not sure what you want");
+ end
+
+
+ function commands.help(session, data)
+ local print = session.print;
+ local section = data:match("^help (%w+)");
+ if not section then
+ print [[Commands are divided into multiple sections. For help on a particular section, ]]
+ print [[type: help SECTION (for example, 'help c2s'). Sections are: ]]
+ print [[]]
+ print [[c2s - Commands to manage local client-to-server sessions]]
+ print [[s2s - Commands to manage sessions between this server and others]]
+ print [[module - Commands to load/reload/unload modules/plugins]]
+ print [[host - Commands to activate, deactivate and list virtual hosts]]
+ print [[server - Uptime, version, shutting down, etc.]]
+ print [[config - Reloading the configuration, etc.]]
+ print [[console - Help regarding the console itself]]
+ elseif section == "c2s" then
+ print [[c2s:show(jid) - Show all client sessions with the specified JID (or all if no JID given)]]
+ print [[c2s:show_insecure() - Show all unencrypted client connections]]
+ print [[c2s:show_secure() - Show all encrypted client connections]]
+ print [[c2s:close(jid) - Close all sessions for the specified JID]]
+ elseif section == "s2s" then
+ print [[s2s:show(domain) - Show all s2s connections for the given domain (or all if no domain given)]]
+ print [[s2s:close(from, to) - Close a connection from one domain to another]]
+ elseif section == "module" then
+ print [[module:load(module, host) - Load the specified module on the specified host (or all hosts if none given)]]
+ print [[module:reload(module, host) - The same, but unloads and loads the module (saving state if the module supports it)]]
+ print [[module:unload(module, host) - The same, but just unloads the module from memory]]
+ print [[module:list(host) - List the modules loaded on the specified host]]
+ elseif section == "host" then
+ print [[host:activate(hostname) - Activates the specified host]]
+ print [[host:deactivate(hostname) - Disconnects all clients on this host and deactivates]]
+ print [[host:list() - List the currently-activated hosts]]
+ elseif section == "server" then
+ print [[server:version() - Show the server's version number]]
+ print [[server:uptime() - Show how long the server has been running]]
+ print [[server:shutdown(reason) - Shut down the server, with an optional reason to be broadcast to all connections]]
+ elseif section == "config" then
+ print [[config:reload() - Reload the server configuration. Modules may need to be reloaded for changes to take effect.]]
+ elseif section == "console" then
+ print [[Hey! Welcome to Prosody's admin console.]]
+ print [[First thing, if you're ever wondering how to get out, simply type 'quit'.]]
+ print [[Secondly, note that we don't support the full telnet protocol yet (it's coming)]]
+ print [[so you may have trouble using the arrow keys, etc. depending on your system.]]
+ print [[]]
+ print [[For now we offer a couple of handy shortcuts:]]
+ print [[!! - Repeat the last command]]
+ print [[!old!new! - repeat the last command, but with 'old' replaced by 'new']]
+ print [[]]
+ print [[For those well-versed in Prosody's internals, or taking instruction from those who are,]]
+ print [[you can prefix a command with > to escape the console sandbox, and access everything in]]
+ print [[the running server. Great fun, but be careful not to break anything :)]]
+ end
+ print [[]]
+ end
+
+ -- Session environment --
+ -- Anything in def_env will be accessible within the session as a global variable
+
+ def_env.server = {};
+
+ function def_env.server:insane_reload()
+ prosody.unlock_globals();
+ dofile "prosody"
+ prosody = _G.prosody;
+ return true, "Server reloaded";
+ end
+
+ function def_env.server:version()
+ return true, tostring(prosody.version or "unknown");
+ end
+
+ function def_env.server:uptime()
+ local t = os.time()-prosody.start_time;
+ local seconds = t%60;
+ t = (t - seconds)/60;
+ local minutes = t%60;
+ t = (t - minutes)/60;
+ local hours = t%24;
+ t = (t - hours)/24;
+ local days = t;
+ return true, string.format("This server has been running for %d day%s, %d hour%s and %d minute%s (since %s)",
+ days, (days ~= 1 and "s") or "", hours, (hours ~= 1 and "s") or "",
+ minutes, (minutes ~= 1 and "s") or "", os.date("%c", prosody.start_time));
+ end
+
+ function def_env.server:shutdown(reason)
+ prosody.shutdown(reason);
+ return true, "Shutdown initiated";
+ end
+
+ def_env.module = {};
+
+ local function get_hosts_set(hosts, module)
+ if type(hosts) == "table" then
+ if hosts[1] then
+ return set.new(hosts);
+ elseif hosts._items then
+ return hosts;
+ end
+ elseif type(hosts) == "string" then
+ return set.new { hosts };
+ elseif hosts == nil then
+ local mm = require "modulemanager";
+ return set.new(array.collect(keys(prosody.hosts)))
+ / function (host) return prosody.hosts[host].type == "local" or module and mm.is_loaded(host, module); end;
+ end
+ end
+
+ function def_env.module:load(name, hosts, config)
+ local mm = require "modulemanager";
+
+ hosts = get_hosts_set(hosts);
+
+ -- Load the module for each host
+ local ok, err, count = true, nil, 0;
+ for host in hosts do
+ if (not mm.is_loaded(host, name)) then
+ ok, err = mm.load(host, name, config);
+ if not ok then
+ ok = false;
+ self.session.print(err or "Unknown error loading module");
+ else
+ count = count + 1;
+ self.session.print("Loaded for "..host);
+ end
+ end
+ end
+
+ return ok, (ok and "Module loaded onto "..count.." host"..(count ~= 1 and "s" or "")) or ("Last error: "..tostring(err));
+ end
+
+ function def_env.module:unload(name, hosts)
+ local mm = require "modulemanager";
+
+ hosts = get_hosts_set(hosts, name);
+
+ -- Unload the module for each host
+ local ok, err, count = true, nil, 0;
+ for host in hosts do
+ if mm.is_loaded(host, name) then
+ ok, err = mm.unload(host, name);
+ if not ok then
+ ok = false;
+ self.session.print(err or "Unknown error unloading module");
+ else
+ count = count + 1;
+ self.session.print("Unloaded from "..host);
+ end
+ end
+ end
+ return ok, (ok and "Module unloaded from "..count.." host"..(count ~= 1 and "s" or "")) or ("Last error: "..tostring(err));
+ end
+
+ function def_env.module:reload(name, hosts)
+ local mm = require "modulemanager";
+
+ hosts = get_hosts_set(hosts, name);
+
+ -- Reload the module for each host
+ local ok, err, count = true, nil, 0;
+ for host in hosts do
+ if mm.is_loaded(host, name) then
+ ok, err = mm.reload(host, name);
+ if not ok then
+ ok = false;
+ self.session.print(err or "Unknown error reloading module");
+ else
+ count = count + 1;
+ if ok == nil then
+ ok = true;
+ end
+ self.session.print("Reloaded on "..host);
+ end
+ end
+ end
+ return ok, (ok and "Module reloaded on "..count.." host"..(count ~= 1 and "s" or "")) or ("Last error: "..tostring(err));
+ end
+
+ function def_env.module:list(hosts)
+ if hosts == nil then
+ hosts = array.collect(keys(prosody.hosts));
+ end
+ if type(hosts) == "string" then
+ hosts = { hosts };
+ end
+ if type(hosts) ~= "table" then
+ return false, "Please supply a host or a list of hosts you would like to see";
+ end
+
+ local print = self.session.print;
+ for _, host in ipairs(hosts) do
+ print(host..":");
+ local modules = array.collect(keys(prosody.hosts[host] and prosody.hosts[host].modules or {})):sort();
+ if #modules == 0 then
+ if prosody.hosts[host] then
+ print(" No modules loaded");
+ else
+ print(" Host not found");
+ end
+ else
+ for _, name in ipairs(modules) do
+ print(" "..name);
+ end
+ end
+ end
+ end
+
+ def_env.config = {};
+ function def_env.config:load(filename, format)
+ local config_load = require "core.configmanager".load;
+ local ok, err = config_load(filename, format);
+ if not ok then
+ return false, err or "Unknown error loading config";
+ end
+ return true, "Config loaded";
+ end
+
+ function def_env.config:get(host, section, key)
+ local config_get = require "core.configmanager".get
+ return true, tostring(config_get(host, section, key));
+ end
+
+ function def_env.config:reload()
+ local ok, err = prosody.reload_config();
+ return ok, (ok and "Config reloaded (you may need to reload modules to take effect)") or tostring(err);
+ end
+
+ def_env.hosts = {};
+ function def_env.hosts:list()
+ for host, host_session in pairs(hosts) do
+ self.session.print(host);
+ end
+ return true, "Done";
+ end
+
+ function def_env.hosts:add(name)
+ end
+
+ def_env.c2s = {};
+
+ local function show_c2s(callback)
+ for hostname, host in pairs(hosts) do
+ for username, user in pairs(host.sessions or {}) do
+ for resource, session in pairs(user.sessions or {}) do
+ local jid = username.."@"..hostname.."/"..resource;
+ callback(jid, session);
+ end
+ end
+ end
+ end
+
+ function def_env.c2s:show(match_jid)
+ local print, count = self.session.print, 0;
+ local curr_host;
+ show_c2s(function (jid, session)
+ if curr_host ~= session.host then
+ curr_host = session.host;
+ print(curr_host);
+ end
+ if (not match_jid) or jid:match(match_jid) then
+ count = count + 1;
+ local status, priority = "unavailable", tostring(session.priority or "-");
+ if session.presence then
+ status = session.presence:child_with_name("show");
+ if status then
+ status = status:get_text() or "[invalid!]";
+ else
+ status = "available";
+ end
+ end
+ print(" "..jid.." - "..status.."("..priority..")");
+ end
+ end);
+ return true, "Total: "..count.." clients";
+ end
+
+ function def_env.c2s:show_insecure(match_jid)
+ local print, count = self.session.print, 0;
+ show_c2s(function (jid, session)
+ if ((not match_jid) or jid:match(match_jid)) and not session.secure then
+ count = count + 1;
+ print(jid);
+ end
+ end);
+ return true, "Total: "..count.." insecure client connections";
+ end
+
+ function def_env.c2s:show_secure(match_jid)
+ local print, count = self.session.print, 0;
+ show_c2s(function (jid, session)
+ if ((not match_jid) or jid:match(match_jid)) and session.secure then
+ count = count + 1;
+ print(jid);
+ end
+ end);
+ return true, "Total: "..count.." secure client connections";
+ end
+
+ function def_env.c2s:close(match_jid)
+ local print, count = self.session.print, 0;
+ show_c2s(function (jid, session)
+ if jid == match_jid or jid_bare(jid) == match_jid then
+ count = count + 1;
+ session:close();
+ end
+ end);
+ return true, "Total: "..count.." sessions closed";
+ end
+
+ def_env.s2s = {};
+ function def_env.s2s:show(match_jid)
+ local _print = self.session.print;
+ local print = self.session.print;
+
+ local count_in, count_out = 0,0;
+
+ for host, host_session in pairs(hosts) do
+ print = function (...) _print(host); _print(...); print = _print; end
+ 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.." <- "..(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.." -> "..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
+ if session.type == "s2sout_unauthed" then
+ if session.connecting then
+ print(" Connection not yet established");
+ if not session.srv_hosts then
+ if not session.conn then
+ print(" We do not yet have a DNS answer for this host's SRV records");
+ else
+ print(" This host has no SRV records, using A record instead");
+ end
+ elseif session.srv_choice then
+ print(" We are on SRV record "..session.srv_choice.." of "..#session.srv_hosts);
+ local srv_choice = session.srv_hosts[session.srv_choice];
+ print(" Using "..(srv_choice.target or ".")..":"..(srv_choice.port or 5269));
+ end
+ elseif session.notopen then
+ print(" The <stream> has not yet been opened");
+ elseif not session.dialback_key then
+ print(" Dialback has not been initiated yet");
+ elseif session.dialback_key then
+ print(" Dialback has been requested, but no result received");
+ end
+ end
+ end
+ end
+ local subhost_filter = function (h)
+ return (match_jid and h:match(match_jid));
+ end
+ for session in pairs(incoming_s2s) do
+ if session.to_host == host and ((not match_jid) or host:match(match_jid)
+ or (session.from_host and session.from_host:match(match_jid))
+ -- Pft! is what I say to list comprehensions
+ or (session.hosts and #array.collect(keys(session.hosts)):filter(subhost_filter)>0)) then
+ count_in = count_in + 1;
-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
-
++ 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
+ for name in pairs(session.hosts) do
+ if name ~= session.from_host then
+ print(" also hosts "..tostring(name));
+ end
+ end
+ end
+ end
+
+ print = _print;
+ end
+
+ for session in pairs(incoming_s2s) do
+ if not session.to_host and ((not match_jid) or session.from_host and session.from_host:match(match_jid)) then
+ count_in = count_in + 1;
+ print("Other incoming s2s connections");
+ print(" (unknown) <- "..(session.from_host or "(unknown)"));
+ end
+ end
+
+ return true, "Total: "..count_out.." outgoing, "..count_in.." incoming connections";
+ end
+
+ function def_env.s2s:close(from, to)
+ local print, count = self.session.print, 0;
+
+ if not (from and to) then
+ return false, "Syntax: s2s:close('from', 'to') - Closes all s2s sessions from 'from' to 'to'";
+ elseif from == to then
+ return false, "Both from and to are the same... you can't do that :)";
+ end
+
+ if hosts[from] and not hosts[to] then
+ -- Is an outgoing connection
+ local session = hosts[from].s2sout[to];
+ if not session then
+ print("No outgoing connection from "..from.." to "..to)
+ else
+ (session.close or s2smanager.destroy_session)(session);
+ count = count + 1;
+ print("Closed outgoing session from "..from.." to "..to);
+ end
+ elseif hosts[to] and not hosts[from] then
+ -- Is an incoming connection
+ for session in pairs(incoming_s2s) do
+ if session.to_host == to and session.from_host == from then
+ (session.close or s2smanager.destroy_session)(session);
+ count = count + 1;
+ end
+ end
+
+ if count == 0 then
+ print("No incoming connections from "..from.." to "..to);
+ else
+ print("Closed "..count.." incoming session"..((count == 1 and "") or "s").." from "..from.." to "..to);
+ end
+ elseif hosts[to] and hosts[from] then
+ return false, "Both of the hostnames you specified are local, there are no s2s sessions to close";
+ else
+ return false, "Neither of the hostnames you specified are being used on this server";
+ end
+
+ return true, "Closed "..count.." s2s session"..((count == 1 and "") or "s");
+ end
+
+ def_env.host = {}; def_env.hosts = def_env.host;
+
+ function def_env.host:activate(hostname, config)
+ return hostmanager.activate(hostname, config);
+ end
+ function def_env.host:deactivate(hostname, reason)
+ return hostmanager.deactivate(hostname, reason);
+ end
+
+ function def_env.host:list()
+ local print = self.session.print;
+ local i = 0;
+ for host in values(array.collect(keys(prosody.hosts)):sort()) do
+ i = i + 1;
+ print(host);
+ end
+ return true, i.." hosts";
+ end
+
+ -------------
+
+ function printbanner(session)
+ local option = config.get("*", "core", "console_banner");
+ if option == nil or option == "full" or option == "graphic" then
+ session.print [[
+ ____ \ / _
+ | _ \ _ __ ___ ___ _-_ __| |_ _
+ | |_) | '__/ _ \/ __|/ _ \ / _` | | | |
+ | __/| | | (_) \__ \ |_| | (_| | |_| |
+ |_| |_| \___/|___/\___/ \__,_|\__, |
+ A study in simplicity |___/
+
+ ]]
+ end
+ if option == nil or option == "short" or option == "full" then
+ session.print("Welcome to the Prosody administration console. For a list of commands, type: help");
+ session.print("You may find more help on using this console in our online documentation at ");
+ session.print("http://prosody.im/doc/console\n");
+ end
+ if option and option ~= "short" and option ~= "full" and option ~= "graphic" then
+ if type(option) == "string" then
+ session.print(option)
+ elseif type(option) == "function" then
+ setfenv(option, redirect_output(_G, session));
+ pcall(option, session);
+ end
+ end
+ end
+
+ prosody.net_activate_ports("console", "console", {5582}, "tcp");
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;
if stanza.attr.type == "valid" then
s2s_make_authenticated(origin, attr.from);
else
- s2s_destroy_session(origin)
+ origin:close("not-authorized", "dialback authentication failed");
end
- end);
+ return true;
+ 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, 100);
- 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' }):tag("optional"):up():up();
- end);
- 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");
- 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 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);
-
+ 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: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(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: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:add_iq_handler("c2s", "urn:ietf:params:xml:ns:xmpp-bind", function(session, stanza)
- log("debug", "Client requesting a resource bind");
-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: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
++ return 1; /* TODO return error message */
+ }
+ strcpy(string, s);
- ret = stringprep(string, 1024, 0, profile);
++ 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
++ 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 }
+};
- if (U_FAILURE(err)) {
- luah_pushnil(L);
- return 1;
- }
++#else
+ #include <unicode/usprep.h>
+ #include <unicode/ustring.h>
+ #include <unicode/utrace.h>
+
+ static int icu_stringprep_prep(lua_State *L, const UStringPrepProfile *profile)
+ {
+ size_t input_len;
+ int32_t unprepped_len, prepped_len, output_len;
+ const char *input;
+ char output[1024];
+
+ UChar unprepped[1024]; /* Temporary unicode buffer (1024 characters) */
+ UChar prepped[1024];
+
+ UErrorCode err = U_ZERO_ERROR;
+
+ if(!lua_isstring(L, 1)) {
+ lua_pushnil(L);
+ return 1;
+ }
+ input = lua_tolstring(L, 1, &input_len);
+ if (input_len >= 1024) {
+ lua_pushnil(L);
+ return 1;
+ }
+ u_strFromUTF8(unprepped, 1024, &unprepped_len, input, input_len, &err);
- if (U_SUCCESS(err) && output_len < 1024)
+ 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);
-#else /* USE_STRINGPREP_ICU */
++ if(output_len < 1024)
+ lua_pushlstring(L, output, output_len);
+ else
+ lua_pushnil(L);
+ return 1;
+ }
+ }
+
+ UStringPrepProfile *icu_nameprep;
+ UStringPrepProfile *icu_nodeprep;
+ UStringPrepProfile *icu_resourceprep;
+ UStringPrepProfile *icu_saslprep;
+
+ /* initialize global ICU stringprep profiles */
+ void init_icu()
+ {
+ UErrorCode err = U_ZERO_ERROR;
+ utrace_setLevel(UTRACE_VERBOSE);
+ icu_nameprep = usprep_openByType(USPREP_RFC3491_NAMEPREP, &err);
+ icu_nodeprep = usprep_openByType(USPREP_RFC3920_NODEPREP, &err);
+ icu_resourceprep = usprep_openByType(USPREP_RFC3920_RESOURCEPREP, &err);
+ icu_saslprep = usprep_openByType(USPREP_RFC4013_SASLPREP, &err);
+ if (U_FAILURE(err)) fprintf(stderr, "[c] util.encodings: error: %s\n", u_errorName((UErrorCode)err));
+ }
+
+ #define MAKE_PREP_FUNC(myFunc, prep) \
+ static int myFunc(lua_State *L) { return icu_stringprep_prep(L, prep); }
+
+ MAKE_PREP_FUNC(Lstringprep_nameprep, icu_nameprep) /** stringprep.nameprep(s) */
+ MAKE_PREP_FUNC(Lstringprep_nodeprep, icu_nodeprep) /** stringprep.nodeprep(s) */
+ MAKE_PREP_FUNC(Lstringprep_resourceprep, icu_resourceprep) /** stringprep.resourceprep(s) */
+ MAKE_PREP_FUNC(Lstringprep_saslprep, icu_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 *****************/
++#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);
- return 1; // TODO return error message
+ 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);
- return 1; // TODO return error message
+ idn_free(output);
-
-#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
+ return 1; /* TODO return error message */
+ }
+ }
- if (U_FAILURE(err)) {
- lua_pushnil(L);
- return 1;
- }
-
++#else
+ #include <unicode/ustdio.h>
+ #include <unicode/uidna.h>
+ /* IDNA2003 or IDNA2008 ? ? ? */
+ static int Lidna_to_ascii(lua_State *L) /** idna.to_ascii(s) */
+ {
+ size_t len;
+ int32_t ulen, dest_len, output_len;
+ const char *s = luaL_checklstring(L, 1, &len);
+ UChar ustr[1024];
+ UErrorCode err = U_ZERO_ERROR;
+ UChar dest[1024];
+ char output[1024];
+
+ u_strFromUTF8(ustr, 1024, &ulen, s, len, &err);
- if (U_SUCCESS(err) && output_len < 1024)
+ 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);
- UChar ustr[1024];
++ if(output_len < 1024)
+ lua_pushlstring(L, output, output_len);
+ else
+ lua_pushnil(L);
+ return 1;
+ }
+ }
+
+ static int Lidna_to_unicode(lua_State *L) /** idna.to_unicode(s) */
+ {
+ size_t len;
+ int32_t ulen, dest_len, output_len;
+ const char *s = luaL_checklstring(L, 1, &len);
- if (U_FAILURE(err)) {
- lua_pushnil(L);
- return 1;
- }
-
++ UChar* ustr;
+ UErrorCode err = U_ZERO_ERROR;
+ UChar dest[1024];
+ char output[1024];
+
+ u_strFromUTF8(ustr, 1024, &ulen, s, len, &err);
- if (U_SUCCESS(err) && output_len < 1024)
+ 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(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[] =
{