aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xconfigure5
-rw-r--r--core/loggingmanager.lua2
-rw-r--r--core/modulemanager.lua2
-rw-r--r--core/s2smanager.lua178
-rw-r--r--core/sessionmanager.lua1
-rw-r--r--core/usermanager.lua6
-rw-r--r--net/dns.lua1
-rw-r--r--net/httpserver.lua1
-rw-r--r--net/server_event.lua9
-rw-r--r--net/server_select.lua22
-rw-r--r--net/xmppcomponent_listener.lua2
-rw-r--r--net/xmppserver_listener.lua4
-rw-r--r--plugins/adhoc/mod_adhoc.lua16
-rw-r--r--plugins/mod_bosh.lua54
-rw-r--r--plugins/mod_component.lua4
-rw-r--r--plugins/mod_compression.lua6
-rw-r--r--plugins/mod_tls.lua2
-rwxr-xr-xprosodyctl101
-rw-r--r--tests/test.lua1
-rw-r--r--tests/test_util_rfc3484.lua51
-rw-r--r--tools/ejabberdsql2prosody.lua7
-rw-r--r--tools/migration/migrator/jabberd14.lua15
-rw-r--r--util/array.lua49
-rw-r--r--util/datamanager.lua6
-rw-r--r--util/debug.lua1
-rw-r--r--util/dependencies.lua8
-rw-r--r--util/ip.lua176
-rw-r--r--util/iterators.lua2
-rw-r--r--util/prosodyctl.lua15
-rw-r--r--util/rfc3484.lua133
-rw-r--r--util/stanza.lua5
-rw-r--r--util/template.lua16
-rw-r--r--util/x509.lua109
-rw-r--r--util/xmppstream.lua48
34 files changed, 888 insertions, 170 deletions
diff --git a/configure b/configure
index af046223..dd6af49c 100755
--- a/configure
+++ b/configure
@@ -112,7 +112,7 @@ do
CFLAGS="-Wall -fPIC"
LDFLAGS="-shared"
fi
- if [ "$OSTYPE" = "freebsd" ]
+ if [ "$OSTYPE" = "freebsd" -o "$OSTYPE" = "openbsd" ]
then LUA_INCDIR="/usr/local/include/lua51"
LUA_INCDIR_SET=yes
CFLAGS="-Wall -fPIC -I/usr/local/include"
@@ -122,6 +122,9 @@ do
LUA_DIR=/usr/local
LUA_DIR_SET=yes
fi
+ if [ "$OSTYPE" = "openbsd" ]
+ then LUA_INCDIR="/usr/local/include";
+ fi
;;
--datadir=*)
DATADIR="$value"
diff --git a/core/loggingmanager.lua b/core/loggingmanager.lua
index 88f2bbbf..426425c1 100644
--- a/core/loggingmanager.lua
+++ b/core/loggingmanager.lua
@@ -41,7 +41,7 @@ local logging_config;
local apply_sink_rules;
local log_sink_types = setmetatable({}, { __newindex = function (t, k, v) rawset(t, k, v); apply_sink_rules(k); end; });
local get_levels;
-local logging_levels = { "debug", "info", "warn", "error", "critical" }
+local logging_levels = { "debug", "info", "warn", "error" }
-- Put a rule into action. Requires that the sink type has already been registered.
-- This function is called automatically when a new sink type is added [see apply_sink_rules()]
diff --git a/core/modulemanager.lua b/core/modulemanager.lua
index 2d1eeb77..c4d95695 100644
--- a/core/modulemanager.lua
+++ b/core/modulemanager.lua
@@ -117,7 +117,7 @@ function load(host, module_name, config)
end
local _log = logger.init(host..":"..module_name);
- local api_instance = setmetatable({ name = module_name, host = host, path = err, config = config, _log = _log, log = function (self, ...) return _log(...); end }, { __index = api });
+ local api_instance = setmetatable({ name = module_name, host = host, path = err, _log = _log, log = function (self, ...) return _log(...); end }, { __index = api });
local pluginenv = setmetatable({ module = api_instance }, { __index = _G });
api_instance.environment = pluginenv;
diff --git a/core/s2smanager.lua b/core/s2smanager.lua
index e3c536f4..acb0baeb 100644
--- a/core/s2smanager.lua
+++ b/core/s2smanager.lua
@@ -16,18 +16,19 @@ local socket = require "socket";
local format = string.format;
local t_insert, t_sort = table.insert, table.sort;
local get_traceback = debug.traceback;
-local tostring, pairs, ipairs, getmetatable, newproxy, error, tonumber, setmetatable
- = tostring, pairs, ipairs, getmetatable, newproxy, error, tonumber, setmetatable;
+local tostring, pairs, ipairs, getmetatable, newproxy, type, error, tonumber, setmetatable
+ = tostring, pairs, ipairs, getmetatable, newproxy, type, error, tonumber, setmetatable;
local idna_to_ascii = require "util.encodings".idna.to_ascii;
local connlisteners_get = require "net.connlisteners".get;
local initialize_filters = require "util.filters".initialize;
local wrapclient = require "net.server".wrapclient;
-local modulemanager = require "core.modulemanager";
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 new_ip = require "util.ip".new_ip;
+local rfc3484_dest = require "util.rfc3484".destination;
local fire_event = prosody.events.fire_event;
local uuid_gen = require "util.uuid".generate;
@@ -43,6 +44,7 @@ local config = require "core.configmanager";
local connect_timeout = config.get("*", "core", "s2s_timeout") or 60;
local dns_timeout = config.get("*", "core", "dns_timeout") or 15;
local max_dns_depth = config.get("*", "core", "dns_max_depth") or 3;
+local sources;
dns.settimeout(dns_timeout);
@@ -243,6 +245,11 @@ function attempt_connection(host_session, err)
for _, record in ipairs(answer) do
t_insert(srv_hosts, record.srv);
end
+ if #srv_hosts == 1 and srv_hosts[1].target == "." then
+ log("debug", to_host.." does not provide a XMPP service");
+ destroy_session(host_session, err); -- Nothing to see here
+ return;
+ end
t_sort(srv_hosts, compare_srv_priorities);
local srv_choice = srv_hosts[1];
@@ -265,6 +272,8 @@ function attempt_connection(host_session, err)
end, "_xmpp-server._tcp."..connect_host..".", "SRV");
return true; -- Attempt in progress
+ elseif host_session.ip_hosts then
+ return try_connect(host_session, connect_host, connect_port, err);
elseif host_session.srv_hosts and #host_session.srv_hosts > host_session.srv_choice then -- Not our first attempt, and we also have SRV
host_session.srv_choice = host_session.srv_choice + 1;
local srv_choice = host_session.srv_hosts[host_session.srv_choice];
@@ -285,54 +294,147 @@ function attempt_connection(host_session, err)
return try_connect(host_session, connect_host, connect_port);
end
-function try_connect(host_session, connect_host, connect_port)
+function try_next_ip(host_session)
+ host_session.connecting = nil;
+ host_session.ip_choice = host_session.ip_choice + 1;
+ local ip = host_session.ip_hosts[host_session.ip_choice];
+ local ok, err= make_connect(host_session, ip.ip, ip.port);
+ if not ok then
+ if not attempt_connection(host_session, err or "closed") then
+ err = err and (": "..err) or "";
+ destroy_session(host_session, "Connection failed"..err);
+ end
+ end
+end
+
+function try_connect(host_session, connect_host, connect_port, err)
host_session.connecting = true;
- local handle;
- handle = adns.lookup(function (reply, err)
- handle = nil;
- host_session.connecting = nil;
-
- -- COMPAT: This is a compromise for all you CNAME-(ab)users :)
- if not (reply and reply[#reply] and reply[#reply].a) then
- local count = max_dns_depth;
- reply = dns.peek(connect_host, "CNAME", "IN");
- while count > 0 and reply and reply[#reply] and not reply[#reply].a and reply[#reply].cname do
- log("debug", "Looking up %s (DNS depth is %d)", tostring(reply[#reply].cname), count);
- reply = dns.peek(reply[#reply].cname, "A", "IN") or dns.peek(reply[#reply].cname, "CNAME", "IN");
- count = count - 1;
+
+ if not err then
+ local IPs = {};
+ host_session.ip_hosts = IPs;
+ local handle4, handle6;
+ local has_other = false;
+
+ if not sources then
+ sources = {};
+ local cfg_sources = config.get("*", "core", "interface") or connlisteners_get("xmppserver").default_interface;
+ if type(cfg_sources) == "string" then
+ cfg_sources = { cfg_sources };
+ end
+ for i, source in ipairs(cfg_sources) do
+ if source == "*" then
+ sources[i] = new_ip("0.0.0.0", "IPv4");
+ else
+ sources[i] = new_ip(source, (source:find(":") and "IPv6") or "IPv4");
+ end
end
end
- -- end of CNAME resolving
-
- if reply and reply[#reply] and reply[#reply].a then
- log("debug", "DNS reply for %s gives us %s", connect_host, reply[#reply].a);
- local ok, err = make_connect(host_session, reply[#reply].a, connect_port);
- if not ok then
- if not attempt_connection(host_session, err or "closed") then
- err = err and (": "..err) or "";
- destroy_session(host_session, "Connection failed"..err);
+
+ handle4 = adns.lookup(function (reply, err)
+ handle4 = nil;
+
+ -- COMPAT: This is a compromise for all you CNAME-(ab)users :)
+ if not (reply and reply[#reply] and reply[#reply].a) then
+ local count = max_dns_depth;
+ reply = dns.peek(connect_host, "CNAME", "IN");
+ while count > 0 and reply and reply[#reply] and not reply[#reply].a and reply[#reply].cname do
+ log("debug", "Looking up %s (DNS depth is %d)", tostring(reply[#reply].cname), count);
+ reply = dns.peek(reply[#reply].cname, "A", "IN") or dns.peek(reply[#reply].cname, "CNAME", "IN");
+ count = count - 1;
end
end
- else
- log("debug", "DNS lookup failed to get a response for %s", connect_host);
- if not attempt_connection(host_session, "name resolution failed") then -- Retry if we can
- log("debug", "No other records to try for %s - destroying", host_session.to_host);
- err = err and (": "..err) or "";
- destroy_session(host_session, "DNS resolution failed"..err); -- End of the line, we can't
+ -- end of CNAME resolving
+
+ if reply and reply[#reply] and reply[#reply].a then
+ for _, ip in ipairs(reply) do
+ log("debug", "DNS reply for %s gives us %s", connect_host, ip.a);
+ IPs[#IPs+1] = new_ip(ip.a, "IPv4");
+ end
end
+
+ if has_other then
+ if #IPs > 0 then
+ rfc3484_dest(host_session.ip_hosts, sources);
+ for i = 1, #IPs do
+ IPs[i] = {ip = IPs[i], port = connect_port};
+ end
+ host_session.ip_choice = 0;
+ try_next_ip(host_session);
+ else
+ log("debug", "DNS lookup failed to get a response for %s", connect_host);
+ host_session.ip_hosts = nil;
+ if not attempt_connection(host_session, "name resolution failed") then -- Retry if we can
+ log("debug", "No other records to try for %s - destroying", host_session.to_host);
+ err = err and (": "..err) or "";
+ destroy_session(host_session, "DNS resolution failed"..err); -- End of the line, we can't
+ end
+ end
+ else
+ has_other = true;
+ end
+ end, connect_host, "A", "IN");
+
+ handle6 = adns.lookup(function (reply, err)
+ handle6 = nil;
+
+ if reply and reply[#reply] and reply[#reply].aaaa then
+ for _, ip in ipairs(reply) do
+ log("debug", "DNS reply for %s gives us %s", connect_host, ip.aaaa);
+ IPs[#IPs+1] = new_ip(ip.aaaa, "IPv6");
+ end
+ end
+
+ if has_other then
+ if #IPs > 0 then
+ rfc3484_dest(host_session.ip_hosts, sources);
+ for i = 1, #IPs do
+ IPs[i] = {ip = IPs[i], port = connect_port};
+ end
+ host_session.ip_choice = 0;
+ try_next_ip(host_session);
+ else
+ log("debug", "DNS lookup failed to get a response for %s", connect_host);
+ host_session.ip_hosts = nil;
+ if not attempt_connection(host_session, "name resolution failed") then -- Retry if we can
+ log("debug", "No other records to try for %s - destroying", host_session.to_host);
+ err = err and (": "..err) or "";
+ destroy_session(host_session, "DNS resolution failed"..err); -- End of the line, we can't
+ end
+ end
+ else
+ has_other = true;
+ end
+ end, connect_host, "AAAA", "IN");
+
+ return true;
+ elseif host_session.ip_hosts and #host_session.ip_hosts > host_session.ip_choice then -- Not our first attempt, and we also have IPs left to try
+ try_next_ip(host_session);
+ else
+ host_session.ip_hosts = nil;
+ if not attempt_connection(host_session, "out of IP addresses") then -- Retry if we can
+ log("debug", "No other records to try for %s - destroying", host_session.to_host);
+ err = err and (": "..err) or "";
+ destroy_session(host_session, "Connecting failed"..err); -- End of the line, we can't
+ return false;
end
- end, connect_host, "A", "IN");
+ end
return true;
end
function make_connect(host_session, connect_host, connect_port)
- (host_session.log or log)("info", "Beginning new connection attempt to %s (%s:%d)", host_session.to_host, connect_host, connect_port);
+ (host_session.log or log)("info", "Beginning new connection attempt to %s ([%s]:%d)", host_session.to_host, connect_host.addr, connect_port);
-- Ok, we're going to try to connect
local from_host, to_host = host_session.from_host, host_session.to_host;
- local conn, handler = socket.tcp();
+ local conn, handler;
+ if connect_host.proto == "IPv4" then
+ conn, handler = socket.tcp();
+ elseif socket.tcp6 then
+ conn, handler = socket.tcp6();
+ end
if not conn then
log("warn", "Failed to create outgoing connection, system error: %s", handler);
@@ -340,14 +442,14 @@ function make_connect(host_session, connect_host, connect_port)
end
conn:settimeout(0);
- local success, err = conn:connect(connect_host, connect_port);
+ local success, err = conn:connect(connect_host.addr, connect_port);
if not success and err ~= "timeout" then
- log("warn", "s2s connect() to %s (%s:%d) failed: %s", host_session.to_host, connect_host, connect_port, err);
+ log("warn", "s2s connect() to %s (%s:%d) failed: %s", host_session.to_host, connect_host.addr, connect_port, err);
return false, err;
end
local cl = connlisteners_get("xmppserver");
- conn = wrapclient(conn, connect_host, connect_port, cl, cl.default_mode or 1 );
+ conn = wrapclient(conn, connect_host.addr, connect_port, cl, cl.default_mode or 1 );
host_session.conn = conn;
local filter = initialize_filters(host_session);
diff --git a/core/sessionmanager.lua b/core/sessionmanager.lua
index 1de6c41a..b1ec819f 100644
--- a/core/sessionmanager.lua
+++ b/core/sessionmanager.lua
@@ -16,7 +16,6 @@ local hosts = hosts;
local full_sessions = full_sessions;
local bare_sessions = bare_sessions;
-local modulemanager = require "core.modulemanager";
local logger = require "util.logger";
local log = logger.init("sessionmanager");
local error = error;
diff --git a/core/usermanager.lua b/core/usermanager.lua
index 0152afd7..9e5a016c 100644
--- a/core/usermanager.lua
+++ b/core/usermanager.lua
@@ -11,6 +11,7 @@ local log = require "util.logger".init("usermanager");
local type = type;
local ipairs = ipairs;
local jid_bare = require "util.jid".bare;
+local jid_prep = require "util.jid".prep;
local config = require "core.configmanager";
local hosts = hosts;
local sasl_new = require "util.sasl".new;
@@ -97,6 +98,7 @@ end
function is_admin(jid, host)
if host and not hosts[host] then return false; end
+ if type(jid) ~= "string" then return false; end
local is_admin;
jid = jid_bare(jid);
@@ -108,7 +110,7 @@ function is_admin(jid, host)
if host_admins and host_admins ~= global_admins then
if type(host_admins) == "table" then
for _,admin in ipairs(host_admins) do
- if admin == jid then
+ if jid_prep(admin) == jid then
is_admin = true;
break;
end
@@ -121,7 +123,7 @@ function is_admin(jid, host)
if not is_admin and global_admins then
if type(global_admins) == "table" then
for _,admin in ipairs(global_admins) do
- if admin == jid then
+ if jid_prep(admin) == jid then
is_admin = true;
break;
end
diff --git a/net/dns.lua b/net/dns.lua
index 8f428476..5b32a56c 100644
--- a/net/dns.lua
+++ b/net/dns.lua
@@ -358,6 +358,7 @@ function resolver:name() -- - - - - - - - - - - - - - - - - - - - - - name
local remember, pointers = nil, 0;
local len = self:byte();
local n = {};
+ if len == 0 then return "." end -- Root label
while len > 0 do
if len >= 0xc0 then -- name is "compressed"
pointers = pointers + 1;
diff --git a/net/httpserver.lua b/net/httpserver.lua
index 74f61c56..44e8e24d 100644
--- a/net/httpserver.lua
+++ b/net/httpserver.lua
@@ -7,7 +7,6 @@
--
-local server = require "net.server"
local url_parse = require "socket.url".parse;
local httpstream_new = require "util.httpstream".new;
diff --git a/net/server_event.lua b/net/server_event.lua
index f4745c34..dbf5161f 100644
--- a/net/server_event.lua
+++ b/net/server_event.lua
@@ -295,7 +295,10 @@ do
end
function interface_mt:resume()
- return self:_lock(self.nointerface, false, self.nowriting);
+ self:_lock(self.nointerface, false, self.nowriting);
+ if not self.eventread then
+ self.eventread = addevent( base, self.conn, EV_READ, self.readcallback, cfg.READ_TIMEOUT ); -- register callback
+ end
end
function interface_mt:counter(c)
@@ -642,6 +645,10 @@ do
return -1
end
end
+ if interface.noreading then
+ interface.eventread = nil;
+ return -1;
+ end
return EV_READ, cfg.READ_TIMEOUT
end
end
diff --git a/net/server_select.lua b/net/server_select.lua
index 412d03c0..8802f620 100644
--- a/net/server_select.lua
+++ b/net/server_select.lua
@@ -596,25 +596,23 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport
handler.sendbuffer = handshake
handshake( socket ) -- do handshake
end
- handler.readbuffer = _readbuffer
- handler.sendbuffer = _sendbuffer
-
- if sslctx then
- out_put "server.lua: auto-starting ssl negotiation..."
- handler.autostart_ssl = true;
- handler:starttls(sslctx);
- end
-
- else
- handler.readbuffer = _readbuffer
- handler.sendbuffer = _sendbuffer
end
+
+ handler.readbuffer = _readbuffer
+ handler.sendbuffer = _sendbuffer
send = socket.send
receive = socket.receive
shutdown = ( ssl and id ) or socket.shutdown
_socketlist[ socket ] = handler
_readlistlen = addsocket(_readlist, socket, _readlistlen)
+
+ if sslctx and luasec then
+ out_put "server.lua: auto-starting ssl negotiation..."
+ handler.autostart_ssl = true;
+ handler:starttls(sslctx);
+ end
+
return handler, socket
end
diff --git a/net/xmppcomponent_listener.lua b/net/xmppcomponent_listener.lua
index 90293559..dd7b2b91 100644
--- a/net/xmppcomponent_listener.lua
+++ b/net/xmppcomponent_listener.lua
@@ -7,8 +7,6 @@
--
-local hosts = _G.hosts;
-
local t_concat = table.concat;
local tostring = tostring;
local type = type;
diff --git a/net/xmppserver_listener.lua b/net/xmppserver_listener.lua
index 3af0b962..048c6c7d 100644
--- a/net/xmppserver_listener.lua
+++ b/net/xmppserver_listener.lua
@@ -66,7 +66,7 @@ function stream_callbacks.handlestanza(session, stanza)
end
local sessions = {};
-local xmppserver = { default_port = 5269, default_mode = "*a" };
+local xmppserver = { default_port = 5269, default_mode = "*a", default_interface = "*" };
-- These are session methods --
@@ -178,7 +178,7 @@ end
function xmppserver.ondisconnect(conn, err)
local session = sessions[conn];
if session then
- if err and err ~= "closed" and session.srv_hosts then
+ if err and err ~= "closed" and session.type == "s2sout_unauthed" then
(session.log or log)("debug", "s2s connection attempt failed: %s", err);
if s2s_attempt_connect(session, err) then
(session.log or log)("debug", "...so we're going to try another target");
diff --git a/plugins/adhoc/mod_adhoc.lua b/plugins/adhoc/mod_adhoc.lua
index 6d826338..49d07103 100644
--- a/plugins/adhoc/mod_adhoc.lua
+++ b/plugins/adhoc/mod_adhoc.lua
@@ -90,19 +90,13 @@ module:hook("iq/host/"..xmlns_cmd..":command", function (event)
end
end, 500);
-local function handle_item_added(item)
+local function adhoc_added(event)
+ local item = event.item;
commands[item.node] = item;
end
-module:hook("item-added/adhoc", function (event)
- return handle_item_added(event.item);
-end, 500);
-
-module:hook("item-removed/adhoc", function (event)
+local function adhoc_removed(event)
commands[event.item.node] = nil;
-end, 500);
-
--- Pick up any items that are already added
-for _, item in ipairs(module:get_host_items("adhoc")) do
- handle_item_added(item);
end
+
+module:handle_items("adhoc", adhoc_added, adhoc_removed);
diff --git a/plugins/mod_bosh.lua b/plugins/mod_bosh.lua
index 834b128a..9de27b4b 100644
--- a/plugins/mod_bosh.lua
+++ b/plugins/mod_bosh.lua
@@ -9,7 +9,6 @@
module.host = "*" -- Global module
local hosts = _G.hosts;
-local lxp = require "lxp";
local new_xmpp_stream = require "util.xmppstream".new;
local httpserver = require "net.httpserver";
local sm = require "core.sessionmanager";
@@ -35,6 +34,7 @@ local BOSH_DEFAULT_POLLING = module:get_option_number("bosh_max_polling", 5);
local BOSH_DEFAULT_REQUESTS = module:get_option_number("bosh_max_requests", 2);
local consider_bosh_secure = module:get_option_boolean("consider_bosh_secure");
+local auto_cork = module:get_option_boolean("bosh_auto_cork", false);
local default_headers = { ["Content-Type"] = "text/xml; charset=utf-8" };
@@ -57,7 +57,7 @@ end
local trusted_proxies = module:get_option_set("trusted_proxies", {"127.0.0.1"})._items;
local function get_ip_from_request(request)
- local ip = request.handler:ip();
+ local ip = request.conn:ip();
local forwarded_for = request.headers["x-forwarded-for"];
if forwarded_for then
forwarded_for = forwarded_for..", "..ip;
@@ -91,9 +91,10 @@ function on_destroy_request(request)
end
-- If this session now has no requests open, mark it as inactive
- if #requests == 0 and session.bosh_max_inactive and not inactive_sessions[session] then
- inactive_sessions[session] = os_time();
- (session.log or log)("debug", "BOSH session marked as inactive at %d", inactive_sessions[session]);
+ local max_inactive = session.bosh_max_inactive;
+ if max_inactive and #requests == 0 then
+ inactive_sessions[session] = os_time() + max_inactive;
+ (session.log or log)("debug", "BOSH session marked as inactive (for %ds)", max_inactive);
end
end
end
@@ -119,10 +120,17 @@ function handle_request(method, body, request)
request.on_destroy = on_destroy_request;
local stream = new_xmpp_stream(request, stream_callbacks);
+
-- stream:feed() calls the stream_callbacks, so all stanzas in
-- the body are processed in this next line before it returns.
+ -- In particular, the streamopened() stream callback is where
+ -- much of the session logic happens, because it's where we first
+ -- get to see the 'sid' of this request.
stream:feed(body);
+ -- Stanzas (if any) in the request have now been processed, and
+ -- we take care of the high-level BOSH logic here, including
+ -- giving a response or putting the request "on hold".
local session = sessions[request.sid];
if session then
-- Session was marked as inactive, since we have
@@ -213,9 +221,11 @@ local function bosh_close_stream(session, reason)
held_request:destroy();
end
sessions[session.sid] = nil;
+ inactive_sessions[session] = nil;
sm_destroy_session(session);
end
+-- Handle the <body> tag in the request payload.
function stream_callbacks.streamopened(request, attr)
local sid = attr.sid;
log("debug", "BOSH body open (sid: %s)", sid or "<none>");
@@ -258,7 +268,7 @@ function stream_callbacks.streamopened(request, attr)
end
--log("debug", "Sending BOSH data: %s", tostring(s));
local oldest_request = r[1];
- if oldest_request then
+ if oldest_request and (not(auto_cork) or waiting_requests[oldest_request]) then
log("debug", "We have an open request, so sending on that");
response.body = t_concat({
"<body xmlns='http://jabber.org/protocol/httpbind' ",
@@ -338,14 +348,6 @@ function stream_callbacks.streamopened(request, attr)
session.rid = rid;
end
- if session.notopen then
- local features = st.stanza("stream:features");
- hosts[session.host].events.fire_event("stream-features", { origin = session, features = features });
- fire_event("stream-features", session, features);
- session.send(features);
- session.notopen = nil;
- end
-
if attr.type == "terminate" then
-- Client wants to end this session, which we'll do
-- after processing any stanzas in this request
@@ -355,6 +357,14 @@ function stream_callbacks.streamopened(request, attr)
request.notopen = nil; -- Signals that we accept this opening tag
t_insert(session.requests, request);
request.sid = sid;
+
+ if session.notopen then
+ local features = st.stanza("stream:features");
+ hosts[session.host].events.fire_event("stream-features", { origin = session, features = features });
+ fire_event("stream-features", session, features);
+ session.send(features);
+ session.notopen = nil;
+ end
end
function stream_callbacks.handlestanza(request, stanza)
@@ -402,17 +412,13 @@ function on_timer()
now = now - 3;
local n_dead_sessions = 0;
- for session, inactive_since in pairs(inactive_sessions) do
- if session.bosh_max_inactive then
- if now - inactive_since > session.bosh_max_inactive then
- (session.log or log)("debug", "BOSH client inactive too long, destroying session at %d", now);
- sessions[session.sid] = nil;
- inactive_sessions[session] = nil;
- n_dead_sessions = n_dead_sessions + 1;
- dead_sessions[n_dead_sessions] = session;
- end
- else
+ for session, close_after in pairs(inactive_sessions) do
+ if close_after < now then
+ (session.log or log)("debug", "BOSH client inactive too long, destroying session at %d", now);
+ sessions[session.sid] = nil;
inactive_sessions[session] = nil;
+ n_dead_sessions = n_dead_sessions + 1;
+ dead_sessions[n_dead_sessions] = session;
end
end
diff --git a/plugins/mod_component.lua b/plugins/mod_component.lua
index 97c46a8c..f7d09930 100644
--- a/plugins/mod_component.lua
+++ b/plugins/mod_component.lua
@@ -10,8 +10,6 @@ if module:get_host_type() ~= "component" then
error("Don't load mod_component manually, it should be for a component, please see http://prosody.im/doc/components", 0);
end
-local hosts = _G.hosts;
-
local t_concat = table.concat;
local sha1 = require "util.hashes".sha1;
@@ -23,6 +21,7 @@ local main_session, send;
local function on_destroy(session, err)
if main_session == session then
+ connected = false;
main_session = nil;
send = nil;
session.on_destroy = nil;
@@ -83,6 +82,7 @@ function handle_component_auth(event)
-- If component not already created for this host, create one now
if not main_session then
+ connected = true;
send = session.send;
main_session = session;
session.on_destroy = on_destroy;
diff --git a/plugins/mod_compression.lua b/plugins/mod_compression.lua
index 82403016..62c0fa2e 100644
--- a/plugins/mod_compression.lua
+++ b/plugins/mod_compression.lua
@@ -16,12 +16,8 @@ local xmlns_stream = "http://etherx.jabber.org/streams";
local compression_stream_feature = st.stanza("compression", {xmlns=xmlns_compression_feature}):tag("method"):text("zlib"):up();
local add_filter = require "util.filters".add_filter;
-local compression_level = module:get_option("compression_level");
--- if not defined assume admin wants best compression
-if compression_level == nil then compression_level = 9 end;
+local compression_level = module:get_option_number("compression_level", 7);
-
-compression_level = tonumber(compression_level);
if not compression_level or compression_level < 1 or compression_level > 9 then
module:log("warn", "Invalid compression level in config: %s", tostring(compression_level));
module:log("warn", "Module loading aborted. Compression won't be available.");
diff --git a/plugins/mod_tls.lua b/plugins/mod_tls.lua
index cace2d69..707ae8f5 100644
--- a/plugins/mod_tls.lua
+++ b/plugins/mod_tls.lua
@@ -75,7 +75,7 @@ end);
module:hook_stanza("http://etherx.jabber.org/streams", "features", function (session, stanza)
module:log("debug", "Received features element");
if can_do_tls(session) and stanza:child_with_ns(xmlns_starttls) then
- module:log("%s is offering TLS, taking up the offer...", session.to_host);
+ module:log("debug", "%s is offering TLS, taking up the offer...", session.to_host);
session.sends2s("<starttls xmlns='"..xmlns_starttls.."'/>");
return true;
end
diff --git a/prosodyctl b/prosodyctl
index f0ba68ce..cdeaaf26 100755
--- a/prosodyctl
+++ b/prosodyctl
@@ -236,6 +236,7 @@ local show_message, show_warning = prosodyctl.show_message, prosodyctl.show_warn
local show_usage = prosodyctl.show_usage;
local getchar, getpass = prosodyctl.getchar, prosodyctl.getpass;
local show_yesno = prosodyctl.show_yesno;
+local show_prompt = prosodyctl.show_prompt;
local read_password = prosodyctl.read_password;
local prosodyctl_timeout = (config.get("*", "core", "prosodyctl_timeout") or 5) * 2;
@@ -612,6 +613,106 @@ function commands.unregister(arg)
return 1;
end
+local x509 = require "util.x509";
+local genx509san = x509.genx509san;
+local opensslbaseconf = x509.baseconf;
+local seralizeopensslbaseconf = x509.serialize_conf;
+
+local cert_commands = {};
+
+-- TODO Should this be moved to util.prosodyctl or x509?
+function cert_commands.config(arg)
+ if #arg >= 1 and arg[1] ~= "--help" then
+ local conf_filename = (CFG_DATADIR or ".") .. "/" .. arg[1] .. ".cnf";
+ if os.execute("test -f "..conf_filename) == 0
+ and not show_yesno("Overwrite "..conf_filename .. "?") then
+ return nil, conf_filename;
+ end
+ local conf = opensslbaseconf();
+ conf.subject_alternative_name = genx509san(hosts, config, arg, true)
+ for k, v in pairs(conf.distinguished_name) do
+ local nv;
+ if k == "commonName" then
+ v = arg[1]
+ elseif k == "emailAddress" then
+ v = "xmpp@" .. arg[1];
+ end
+ nv = show_prompt(("%s (%s):"):format(k, nv or v));
+ nv = (not nv or nv == "") and v or nv;
+ conf.distinguished_name[k] = nv ~= "." and nv or nil;
+ end
+ local conf_file = io.open(conf_filename, "w");
+ conf_file:write(seralizeopensslbaseconf(conf));
+ conf_file:close();
+ print("");
+ show_message("Config written to " .. conf_filename);
+ return nil, conf_filename;
+ else
+ show_usage("cert config HOSTNAME", "generates config for OpenSSL")
+ end
+end
+
+function cert_commands.key(arg)
+ if #arg >= 1 and arg[1] ~= "--help" then
+ local key_filename = (CFG_DATADIR or ".") .. "/" .. arg[1] .. ".key";
+ if os.execute("test -f "..key_filename) == 0
+ and not show_yesno("Overwrite "..key_filename .. "?") then
+ return nil, key_filename;
+ end
+ local key_size = tonumber(arg[2] or show_prompt("Choose key size (2048):") or 2048);
+ os.execute(("openssl genrsa -out %s %d"):format(key_filename, tonumber(key_size)));
+ os.execute(("chmod 400 %s"):format(key_filename));
+ show_message("Key written to ".. key_filename);
+ return nil, key_filename;
+ else
+ show_usage("cert key HOSTNAME <bits>", "Generates a RSA key")
+ end
+end
+
+function cert_commands.request(arg)
+ if #arg >= 1 and arg[1] ~= "--help" then
+ local req_filename = (CFG_DATADIR or ".") .. "/" .. arg[1] .. ".req";
+ if os.execute("test -f "..req_filename) == 0
+ and not show_yesno("Overwrite "..req_filename .. "?") then
+ return nil, req_filename;
+ end
+ local _, key_filename = cert_commands.key({arg[1]});
+ local _, conf_filename = cert_commands.config({arg[1]});
+ os.execute(("openssl req -new -key %s -utf8 -config %s -out %s")
+ :format(key_filename, conf_filename, req_filename));
+ show_message("Certificate request written to ".. req_filename);
+ else
+ show_usage("cert request HOSTNAME", "Generates a certificate request")
+ end
+end
+
+function cert_commands.generate(arg)
+ if #arg >= 1 and arg[1] ~= "--help" then
+ local cert_filename = (CFG_DATADIR or ".") .. "/" .. arg[1] .. ".cert";
+ if os.execute("test -f "..cert_filename) == 0
+ and not show_yesno("Overwrite "..cert_filename .. "?") then
+ return nil, cert_filename;
+ end
+ local _, key_filename = cert_commands.key({arg[1]});
+ local _, conf_filename = cert_commands.config({arg[1]});
+ os.execute(("openssl req -new -x509 -nodes -key %s -days 365 -sha1 -utf8 -config %s -out %s")
+ :format(key_filename, conf_filename, cert_filename));
+ show_message("Certificate written to ".. cert_filename);
+ else
+ show_usage("cert generate HOSTNAME", "Generates a self-signed certificate")
+ end
+end
+
+function commands.cert(arg)
+ if #arg >= 1 and arg[1] ~= "--help" then
+ local subcmd = table.remove(arg, 1);
+ if type(cert_commands[subcmd]) == "function" then
+ return cert_commands[subcmd](arg);
+ end
+ end
+ show_usage("cert config|request|generate|key", "Helpers for X.509 certificates.")
+end
+
---------------------
if command and command:match("^mod_") then -- Is a command in a module
diff --git a/tests/test.lua b/tests/test.lua
index 000c3ee9..db727ce1 100644
--- a/tests/test.lua
+++ b/tests/test.lua
@@ -12,6 +12,7 @@ function run_all_tests()
package.loaded["net.connlisteners"] = { get = function () return {} end };
dotest "util.jid"
dotest "util.multitable"
+ dotest "util.rfc3484"
dotest "net.http"
dotest "core.modulemanager"
dotest "core.stanza_router"
diff --git a/tests/test_util_rfc3484.lua b/tests/test_util_rfc3484.lua
new file mode 100644
index 00000000..18ae310e
--- /dev/null
+++ b/tests/test_util_rfc3484.lua
@@ -0,0 +1,51 @@
+-- Prosody IM
+-- Copyright (C) 2011 Florian Zeitz
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+function source(source)
+ local new_ip = require"util.ip".new_ip;
+ assert_equal(source(new_ip("2001::1", "IPv6"), {new_ip("3ffe::1", "IPv6"), new_ip("fe80::1", "IPv6")}).addr, "3ffe::1", "prefer appropriate scope");
+ assert_equal(source(new_ip("2001::1", "IPv6"), {new_ip("fe80::1", "IPv6"), new_ip("fec0::1", "IPv6")}).addr, "fec0::1", "prefer appropriate scope");
+ assert_equal(source(new_ip("fec0::1", "IPv6"), {new_ip("fe80::1", "IPv6"), new_ip("2001::1", "IPv6")}).addr, "2001::1", "prefer appropriate scope");
+ assert_equal(source(new_ip("ff05::1", "IPv6"), {new_ip("fe80::1", "IPv6"), new_ip("fec0::1", "IPv6"), new_ip("2001::1", "IPv6")}).addr, "fec0::1", "prefer appropriate scope");
+ assert_equal(source(new_ip("2001::1", "IPv6"), {new_ip("2001::1", "IPv6"), new_ip("2002::1", "IPv6")}).addr, "2001::1", "prefer same address");
+ assert_equal(source(new_ip("fec0::1", "IPv6"), {new_ip("fec0::2", "IPv6"), new_ip("2001::1", "IPv6")}).addr, "fec0::2", "prefer appropriate scope");
+ assert_equal(source(new_ip("2001::1", "IPv6"), {new_ip("2001::2", "IPv6"), new_ip("3ffe::2", "IPv6")}).addr, "2001::2", "longest matching prefix");
+ assert_equal(source(new_ip("2002:836b:2179::1", "IPv6"), {new_ip("2002:836b:2179::d5e3:7953:13eb:22e8", "IPv6"), new_ip("2001::2", "IPv6")}).addr, "2002:836b:2179::d5e3:7953:13eb:22e8", "prefer matching label");
+end
+
+function destination(dest)
+ local order;
+ local new_ip = require"util.ip".new_ip;
+ order = dest({new_ip("2001::1", "IPv6"), new_ip("131.107.65.121", "IPv4")}, {new_ip("2001::2", "IPv6"), new_ip("fe80::1", "IPv6"), new_ip("169.254.13.78", "IPv4")})
+ assert_equal(order[1].addr, "2001::1", "prefer matching scope");
+ assert_equal(order[2].addr, "131.107.65.121", "prefer matching scope")
+
+ order = dest({new_ip("2001::1", "IPv6"), new_ip("131.107.65.121", "IPv4")}, {new_ip("fe80::1", "IPv6"), new_ip("131.107.65.117", "IPv4")})
+ assert_equal(order[1].addr, "131.107.65.121", "prefer matching scope")
+ assert_equal(order[2].addr, "2001::1", "prefer matching scope")
+
+ order = dest({new_ip("2001::1", "IPv6"), new_ip("10.1.2.3", "IPv4")}, {new_ip("2001::2", "IPv6"), new_ip("fe80::1", "IPv6"), new_ip("10.1.2.4", "IPv4")})
+ assert_equal(order[1].addr, "2001::1", "prefer higher precedence");
+ assert_equal(order[2].addr, "10.1.2.3", "prefer higher precedence");
+
+ order = dest({new_ip("2001::1", "IPv6"), new_ip("fec0::1", "IPv6"), new_ip("fe80::1", "IPv6")}, {new_ip("2001::2", "IPv6"), new_ip("fec0::1", "IPv6"), new_ip("fe80::2", "IPv6")})
+ assert_equal(order[1].addr, "fe80::1", "prefer smaller scope");
+ assert_equal(order[2].addr, "fec0::1", "prefer smaller scope");
+ assert_equal(order[3].addr, "2001::1", "prefer smaller scope");
+
+ order = dest({new_ip("2001::1", "IPv6"), new_ip("3ffe::1", "IPv6")}, {new_ip("2001::2", "IPv6"), new_ip("3f44::2", "IPv6"), new_ip("fe80::2", "IPv6")})
+ assert_equal(order[1].addr, "2001::1", "longest matching prefix");
+ assert_equal(order[2].addr, "3ffe::1", "longest matching prefix");
+
+ order = dest({new_ip("2002:836b:4179::1", "IPv6"), new_ip("2001::1", "IPv6")}, {new_ip("2002:836b:4179::2", "IPv6"), new_ip("fe80::2", "IPv6")})
+ assert_equal(order[1].addr, "2002:836b:4179::1", "prefer matching label");
+ assert_equal(order[2].addr, "2001::1", "prefer matching label");
+
+ order = dest({new_ip("2002:836b:4179::1", "IPv6"), new_ip("2001::1", "IPv6")}, {new_ip("2002:836b:4179::2", "IPv6"), new_ip("2001::2", "IPv6"), new_ip("fe80::2", "IPv6")})
+ assert_equal(order[1].addr, "2001::1", "prefer higher precedence");
+ assert_equal(order[2].addr, "2002:836b:4179::1", "prefer higher precedence");
+end
diff --git a/tools/ejabberdsql2prosody.lua b/tools/ejabberdsql2prosody.lua
index 576f4436..c64faee0 100644
--- a/tools/ejabberdsql2prosody.lua
+++ b/tools/ejabberdsql2prosody.lua
@@ -129,7 +129,12 @@ local function readInsert()
end
end
local tname = readTableName();
- for ch in ("` VALUES "):gmatch(".") do read(ch); end -- expect this
+ read("`"); read(" ") -- expect this
+ if peek() == "(" then -- skip column list
+ repeat until read() == ")";
+ read(" ");
+ end
+ for ch in ("VALUES "):gmatch(".") do read(ch); end -- expect this
local tuples = readTuples();
read(";"); read("\n");
return tname, tuples;
diff --git a/tools/migration/migrator/jabberd14.lua b/tools/migration/migrator/jabberd14.lua
index 47004a52..54c45d94 100644
--- a/tools/migration/migrator/jabberd14.lua
+++ b/tools/migration/migrator/jabberd14.lua
@@ -67,15 +67,12 @@ local parse_xml = (function()
end)();
local function load_xml(path)
- if path then
- local f, err = io_open(path);
- if not f then return f, err; end
- local data = f:read("*a");
- f:close();
- if data then
- return parse_xml(data);
- end
- end
+ local f, err = io_open(path);
+ if not f then return f, err; end
+ local data = f:read("*a");
+ f:close();
+ if not data then return; end
+ return parse_xml(data);
end
local function load_spool_file(host, filename, path)
diff --git a/util/array.lua b/util/array.lua
index 5dbd3037..5dc604ba 100644
--- a/util/array.lua
+++ b/util/array.lua
@@ -9,6 +9,11 @@
local t_insert, t_sort, t_remove, t_concat
= table.insert, table.sort, table.remove, table.concat;
+local setmetatable = setmetatable;
+local math_random = math.random;
+local pairs, ipairs = pairs, ipairs;
+local tostring = tostring;
+
local array = {};
local array_base = {};
local array_methods = {};
@@ -25,6 +30,15 @@ end
setmetatable(array, { __call = new_array });
+-- Read-only methods
+function array_methods:random()
+ return self[math_random(1,#self)];
+end
+
+-- These methods can be called two ways:
+-- array.method(existing_array, [params [, ...]]) -- Create new array for result
+-- existing_array:method([params, ...]) -- Transform existing array into result
+--
function array_base.map(outa, ina, func)
for k,v in ipairs(ina) do
outa[k] = func(v);
@@ -60,15 +74,18 @@ function array_base.sort(outa, ina, ...)
return outa;
end
---- These methods only mutate
-function array_methods:random()
- return self[math.random(1,#self)];
+function array_base.pluck(outa, ina, key)
+ for i=1,#ina do
+ outa[i] = ina[i][key];
+ end
+ return outa;
end
+--- These methods only mutate the array
function array_methods:shuffle(outa, ina)
local len = #self;
for i=1,#self do
- local r = math.random(i,len);
+ local r = math_random(i,len);
self[i], self[r] = self[r], self[i];
end
return self;
@@ -91,10 +108,24 @@ function array_methods:append(array)
return self;
end
-array_methods.push = table.insert;
-array_methods.pop = table.remove;
-array_methods.concat = table.concat;
-array_methods.length = function (t) return #t; end
+function array_methods:push(x)
+ t_insert(self, x);
+ return self;
+end
+
+function array_methods:pop(x)
+ local v = self[x];
+ t_remove(self, x);
+ return v;
+end
+
+function array_methods:concat(sep)
+ return t_concat(array.map(self, tostring), sep);
+end
+
+function array_methods:length()
+ return #self;
+end
--- These methods always create a new array
function array.collect(f, s, var)
@@ -102,7 +133,7 @@ function array.collect(f, s, var)
while true do
var = f(s, var);
if var == nil then break; end
- table.insert(t, var);
+ t_insert(t, var);
end
return setmetatable(t, array_mt);
end
diff --git a/util/datamanager.lua b/util/datamanager.lua
index d5e9c88c..a5d676cc 100644
--- a/util/datamanager.lua
+++ b/util/datamanager.lua
@@ -1,7 +1,7 @@
-- 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.
--
@@ -20,7 +20,7 @@ local error = error;
local next = next;
local t_insert = table.insert;
local append = require "util.serialization".append;
-local path_separator = "/"; if os.getenv("WINDIR") then path_separator = "\\" end
+local path_separator = assert ( package.config:match ( "^([^\n]+)" ) , "package.config not in standard form" ) -- Extract directory seperator from package.config (an undocumented string that comes with lua)
local lfs = require "lfs";
local prosody = prosody;
local raw_mkdir;
@@ -72,7 +72,7 @@ local function callback(username, host, datastore, data)
username, host, datastore, data = f(username, host, datastore, data);
if username == false then break; end
end
-
+
return username, host, datastore, data;
end
function add_callback(func)
diff --git a/util/debug.lua b/util/debug.lua
index 22d02bf2..2170a6d1 100644
--- a/util/debug.lua
+++ b/util/debug.lua
@@ -9,6 +9,7 @@ local censored_names = {
};
local function get_locals_table(level)
+ level = level + 1; -- Skip this function itself
local locals = {};
for local_num = 1, math.huge do
local name, value = debug.getlocal(level, local_num);
diff --git a/util/dependencies.lua b/util/dependencies.lua
index 5baea942..53d2719d 100644
--- a/util/dependencies.lua
+++ b/util/dependencies.lua
@@ -136,6 +136,14 @@ function log_warnings()
log("error", "This version of LuaSec contains a known bug that causes disconnects, see http://prosody.im/doc/depends");
end
end
+ if lxp then
+ if not pcall(lxp.new, { StartDoctypeDecl = false }) then
+ log("error", "The version of LuaExpat on your system leaves Prosody "
+ .."vulnerable to denial-of-service attacks. You should upgrade to "
+ .."LuaExpat 1.1.1 or higher as soon as possible. See "
+ .."http://prosody.im/doc/depends#luaexpat for more information.");
+ end
+ end
end
return _M;
diff --git a/util/ip.lua b/util/ip.lua
new file mode 100644
index 00000000..2f09c034
--- /dev/null
+++ b/util/ip.lua
@@ -0,0 +1,176 @@
+-- Prosody IM
+-- Copyright (C) 2008-2011 Florian Zeitz
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+local ip_methods = {};
+local ip_mt = { __index = function (ip, key) return (ip_methods[key])(ip); end,
+ __tostring = function (ip) return ip.addr; end,
+ __eq = function (ipA, ipB) return ipA.addr == ipB.addr; end};
+local hex2bits = { ["0"] = "0000", ["1"] = "0001", ["2"] = "0010", ["3"] = "0011", ["4"] = "0100", ["5"] = "0101", ["6"] = "0110", ["7"] = "0111", ["8"] = "1000", ["9"] = "1001", ["A"] = "1010", ["B"] = "1011", ["C"] = "1100", ["D"] = "1101", ["E"] = "1110", ["F"] = "1111" };
+
+local function new_ip(ipStr, proto)
+ if proto ~= "IPv4" and proto ~= "IPv6" then
+ return nil, "invalid protocol";
+ end
+
+ return setmetatable({ addr = ipStr, proto = proto }, ip_mt);
+end
+
+local function toBits(ip)
+ local result = "";
+ local fields = {};
+ if ip.proto == "IPv4" then
+ ip = ip.toV4mapped;
+ end
+ ip = (ip.addr):upper();
+ ip:gsub("([^:]*):?", function (c) fields[#fields + 1] = c end);
+ if not ip:match(":$") then fields[#fields] = nil; end
+ for i, field in ipairs(fields) do
+ if field:len() == 0 and i ~= 1 and i ~= #fields then
+ for i = 1, 16 * (9 - #fields) do
+ result = result .. "0";
+ end
+ else
+ for i = 1, 4 - field:len() do
+ result = result .. "0000";
+ end
+ for i = 1, field:len() do
+ result = result .. hex2bits[field:sub(i,i)];
+ end
+ end
+ end
+ return result;
+end
+
+local function commonPrefixLength(ipA, ipB)
+ ipA, ipB = toBits(ipA), toBits(ipB);
+ for i = 1, 128 do
+ if ipA:sub(i,i) ~= ipB:sub(i,i) then
+ return i-1;
+ end
+ end
+ return 128;
+end
+
+local function v4scope(ip)
+ local fields = {};
+ ip:gsub("([^.]*).?", function (c) fields[#fields + 1] = tonumber(c) end);
+ -- Loopback:
+ if fields[1] == 127 then
+ return 0x2;
+ -- Link-local unicast:
+ elseif fields[1] == 169 and fields[2] == 254 then
+ return 0x2;
+ -- Site-local unicast:
+ elseif (fields[1] == 10) or (fields[1] == 192 and fields[2] == 168) or (fields[1] == 172 and (fields[2] >= 16 and fields[2] < 32)) then
+ return 0x5;
+ -- Global unicast:
+ else
+ return 0xE;
+ end
+end
+
+local function v6scope(ip)
+ -- Loopback:
+ if ip:match("^[0:]*1$") then
+ return 0x2;
+ -- Link-local unicast:
+ elseif ip:match("^[Ff][Ee][89ABab]") then
+ return 0x2;
+ -- Site-local unicast:
+ elseif ip:match("^[Ff][Ee][CcDdEeFf]") then
+ return 0x5;
+ -- Multicast:
+ elseif ip:match("^[Ff][Ff]") then
+ return tonumber("0x"..ip:sub(4,4));
+ -- Global unicast:
+ else
+ return 0xE;
+ end
+end
+
+local function label(ip)
+ if commonPrefixLength(ip, new_ip("::1", "IPv6")) == 128 then
+ return 0;
+ elseif commonPrefixLength(ip, new_ip("2002::", "IPv6")) >= 16 then
+ return 2;
+ elseif commonPrefixLength(ip, new_ip("::", "IPv6")) >= 96 then
+ return 3;
+ elseif commonPrefixLength(ip, new_ip("::ffff:0:0", "IPv6")) >= 96 then
+ return 4;
+ else
+ return 1;
+ end
+end
+
+local function precedence(ip)
+ if commonPrefixLength(ip, new_ip("::1", "IPv6")) == 128 then
+ return 50;
+ elseif commonPrefixLength(ip, new_ip("2002::", "IPv6")) >= 16 then
+ return 30;
+ elseif commonPrefixLength(ip, new_ip("::", "IPv6")) >= 96 then
+ return 20;
+ elseif commonPrefixLength(ip, new_ip("::ffff:0:0", "IPv6")) >= 96 then
+ return 10;
+ else
+ return 40;
+ end
+end
+
+local function toV4mapped(ip)
+ local fields = {};
+ local ret = "::ffff:";
+ ip:gsub("([^.]*).?", function (c) fields[#fields + 1] = tonumber(c) end);
+ ret = ret .. ("%02x"):format(fields[1]);
+ ret = ret .. ("%02x"):format(fields[2]);
+ ret = ret .. ":"
+ ret = ret .. ("%02x"):format(fields[3]);
+ ret = ret .. ("%02x"):format(fields[4]);
+ return new_ip(ret, "IPv6");
+end
+
+function ip_methods:toV4mapped()
+ if self.proto ~= "IPv4" then return nil, "No IPv4 address" end
+ local value = toV4mapped(self.addr);
+ self.toV4mapped = value;
+ return value;
+end
+
+function ip_methods:label()
+ local value;
+ if self.proto == "IPv4" then
+ value = label(self.toV4mapped);
+ else
+ value = label(self);
+ end
+ self.label = value;
+ return value;
+end
+
+function ip_methods:precedence()
+ local value;
+ if self.proto == "IPv4" then
+ value = precedence(self.toV4mapped);
+ else
+ value = precedence(self);
+ end
+ self.precedence = value;
+ return value;
+end
+
+function ip_methods:scope()
+ local value;
+ if self.proto == "IPv4" then
+ value = v4scope(self.addr);
+ else
+ value = v6scope(self.addr);
+ end
+ self.scope = value;
+ return value;
+end
+
+return {new_ip = new_ip,
+ commonPrefixLength = commonPrefixLength};
diff --git a/util/iterators.lua b/util/iterators.lua
index 2a87e97a..aa0b172b 100644
--- a/util/iterators.lua
+++ b/util/iterators.lua
@@ -140,7 +140,7 @@ end
-- Treat the return of an iterator as key,value pairs,
-- and build a table
function it2table(f, s, var)
- local t, var = {};
+ local t, var2 = {};
while true do
var, var2 = f(s, var);
if var == nil then break; end
diff --git a/util/prosodyctl.lua b/util/prosodyctl.lua
index 2da39cb2..439de551 100644
--- a/util/prosodyctl.lua
+++ b/util/prosodyctl.lua
@@ -16,6 +16,7 @@ local signal = require "util.signal";
local set = require "util.set";
local lfs = require "lfs";
local pcall = pcall;
+local type = type;
local nodeprep, nameprep = stringprep.nodeprep, stringprep.nameprep;
@@ -63,6 +64,13 @@ function getchar(n)
end
end
+function getline()
+ local ok, line = pcall(io.read, "*l");
+ if ok then
+ return line;
+ end
+end
+
function getpass()
local stty_ret = os.execute("stty -echo 2>/dev/null");
if stty_ret ~= 0 then
@@ -112,6 +120,13 @@ function read_password()
return password;
end
+function show_prompt(prompt)
+ io.write(prompt, " ");
+ local line = getline();
+ line = line and line:gsub("\n$","");
+ return (line and #line > 0) and line or nil;
+end
+
-- Server control
function adduser(params)
local user, host, password = nodeprep(params.user), nameprep(params.host), params.password;
diff --git a/util/rfc3484.lua b/util/rfc3484.lua
new file mode 100644
index 00000000..dd855a84
--- /dev/null
+++ b/util/rfc3484.lua
@@ -0,0 +1,133 @@
+-- Prosody IM
+-- Copyright (C) 2008-2011 Florian Zeitz
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+local commonPrefixLength = require"util.ip".commonPrefixLength
+local new_ip = require"util.ip".new_ip;
+
+local function t_sort(t, comp)
+ for i = 1, (#t - 1) do
+ for j = (i + 1), #t do
+ local a, b = t[i], t[j];
+ if not comp(a,b) then
+ t[i], t[j] = b, a;
+ end
+ end
+ end
+end
+
+function source(dest, candidates)
+ local function comp(ipA, ipB)
+ -- Rule 1: Prefer same address
+ if dest == ipA then
+ return true;
+ elseif dest == ipB then
+ return false;
+ end
+
+ -- Rule 2: Prefer appropriate scope
+ if ipA.scope < ipB.scope then
+ if ipA.scope < dest.scope then
+ return false;
+ else
+ return true;
+ end
+ elseif ipA.scope > ipB.scope then
+ if ipB.scope < dest.scope then
+ return true;
+ else
+ return false;
+ end
+ end
+
+ -- Rule 3: Avoid deprecated addresses
+ -- XXX: No way to determine this
+ -- Rule 4: Prefer home addresses
+ -- XXX: Mobility Address related, no way to determine this
+ -- Rule 5: Prefer outgoing interface
+ -- XXX: Interface to address relation. No way to determine this
+ -- Rule 6: Prefer matching label
+ if ipA.label == dest.label and ipB.label ~= dest.label then
+ return true;
+ elseif ipB.label == dest.label and ipA.label ~= dest.label then
+ return false;
+ end
+
+ -- Rule 7: Prefer public addresses (over temporary ones)
+ -- XXX: No way to determine this
+ -- Rule 8: Use longest matching prefix
+ if commonPrefixLength(ipA, dest) > commonPrefixLength(ipB, dest) then
+ return true;
+ else
+ return false;
+ end
+ end
+
+ t_sort(candidates, comp);
+ return candidates[1];
+end
+
+function destination(candidates, sources)
+ local sourceAddrs = {};
+ local function comp(ipA, ipB)
+ local ipAsource = sourceAddrs[ipA];
+ local ipBsource = sourceAddrs[ipB];
+ -- Rule 1: Avoid unusable destinations
+ -- XXX: No such information
+ -- Rule 2: Prefer matching scope
+ if ipA.scope == ipAsource.scope and ipB.scope ~= ipBsource.scope then
+ return true;
+ elseif ipA.scope ~= ipAsource.scope and ipB.scope == ipBsource.scope then
+ return false;
+ end
+
+ -- Rule 3: Avoid deprecated addresses
+ -- XXX: No way to determine this
+ -- Rule 4: Prefer home addresses
+ -- XXX: Mobility Address related, no way to determine this
+ -- Rule 5: Prefer matching label
+ if ipAsource.label == ipA.label and ipBsource.label ~= ipB.label then
+ return true;
+ elseif ipBsource.label == ipB.label and ipAsource.label ~= ipA.label then
+ return false;
+ end
+
+ -- Rule 6: Prefer higher precedence
+ if ipA.precedence > ipB.precedence then
+ return true;
+ elseif ipA.precedence < ipB.precedence then
+ return false;
+ end
+
+ -- Rule 7: Prefer native transport
+ -- XXX: No way to determine this
+ -- Rule 8: Prefer smaller scope
+ if ipA.scope < ipB.scope then
+ return true;
+ elseif ipA.scope > ipB.scope then
+ return false;
+ end
+
+ -- Rule 9: Use longest matching prefix
+ if commonPrefixLength(ipA, ipAsource) > commonPrefixLength(ipB, ipBsource) then
+ return true;
+ elseif commonPrefixLength(ipA, ipAsource) < commonPrefixLength(ipB, ipBsource) then
+ return false;
+ end
+
+ -- Rule 10: Otherwise, leave order unchanged
+ return true;
+ end
+ for _, ip in ipairs(candidates) do
+ sourceAddrs[ip] = source(ip, sources);
+ end
+
+ t_sort(candidates, comp);
+ return candidates;
+end
+
+return {source = source,
+ destination = destination};
diff --git a/util/stanza.lua b/util/stanza.lua
index de83977f..600212a4 100644
--- a/util/stanza.lua
+++ b/util/stanza.lua
@@ -258,11 +258,6 @@ function stanza_mt.get_error(stanza)
return type, condition or "undefined-condition", text;
end
-function stanza_mt.__add(s1, s2)
- return s1:add_direct_child(s2);
-end
-
-
do
local id = 0;
function new_id()
diff --git a/util/template.lua b/util/template.lua
index ebd8be14..5e9b479e 100644
--- a/util/template.lua
+++ b/util/template.lua
@@ -7,6 +7,7 @@ local ipairs = ipairs;
local error = error;
local loadstring = loadstring;
local debug = debug;
+local t_remove = table.remove;
module("template")
@@ -42,7 +43,6 @@ local parse_xml = (function()
stanza:tag(name, attr);
end
function handler:CharacterData(data)
- data = data:gsub("^%s*", ""):gsub("%s*$", "");
stanza:text(data);
end
function handler:EndElement(tagname)
@@ -60,6 +60,19 @@ local parse_xml = (function()
end;
end)();
+local function trim_xml(stanza)
+ for i=#stanza,1,-1 do
+ local child = stanza[i];
+ if child.name then
+ trim_xml(child);
+ else
+ child = child:gsub("^%s*", ""):gsub("%s*$", "");
+ stanza[i] = child;
+ if child == "" then t_remove(stanza, i); end
+ end
+ end
+end
+
local function create_string_string(str)
str = ("%q"):format(str);
str = str:gsub("{([^}]*)}", function(s)
@@ -118,6 +131,7 @@ local template_mt = { __tostring = function(t) return t.name end };
local function create_template(templates, text)
local stanza, err = parse_xml(text);
if not stanza then error(err); end
+ trim_xml(stanza);
local info = debug.getinfo(3, "Sl");
info = info and ("template(%s:%d)"):format(info.short_src:match("[^\\/]*$"), info.currentline) or "template(unknown)";
diff --git a/util/x509.lua b/util/x509.lua
index d3c55bb4..f106e6fa 100644
--- a/util/x509.lua
+++ b/util/x509.lua
@@ -21,6 +21,10 @@
local nameprep = require "util.encodings".stringprep.nameprep;
local idna_to_ascii = require "util.encodings".idna.to_ascii;
local log = require "util.logger".init("x509");
+local pairs, ipairs = pairs, ipairs;
+local s_format = string.format;
+local t_insert = table.insert;
+local t_concat = table.concat;
module "x509"
@@ -208,4 +212,109 @@ function verify_identity(host, service, cert)
return false
end
+-- TODO Rename? Split out subroutines?
+-- Also, this is probably openssl specific, what TODO about that?
+function genx509san(hosts, config, certhosts, raw) -- recive config through that or some better way?
+ local function utf8string(s)
+ -- This is how we tell openssl not to encode UTF-8 strings as Latin1
+ return s_format("FORMAT:UTF8,UTF8:%s", s);
+ end
+
+ local function ia5string(s)
+ return s_format("IA5STRING:%s", s);
+ end
+
+ local function dnsname(t, host)
+ t_insert(t.DNS, idna_to_ascii(host));
+ end
+
+ local function srvname(t, host, service)
+ t_insert(t.otherName, s_format("%s;%s", oid_dnssrv, ia5string("_" .. service .."." .. idna_to_ascii(host))));
+ end
+
+ local function xmppAddr(t, host)
+ t_insert(t.otherName, s_format("%s;%s", oid_xmppaddr, utf8string(host)));
+ end
+
+ -----------------------------
+
+ local san = {
+ DNS = {};
+ otherName = {};
+ };
+
+ local sslsanconf = { };
+
+ for i = 1,#certhosts do
+ local certhost = certhosts[i];
+ for name, host in pairs(hosts) do
+ if name == certhost or name:sub(-1-#certhost) == "."..certhost then
+ dnsname(san, name);
+ --print(name .. "#component_module: " .. (config.get(name, "core", "component_module") or "nil"));
+ if config.get(name, "core", "component_module") == nil then
+ srvname(san, name, "xmpp-client");
+ end
+ --print(name .. "#anonymous_login: " .. tostring(config.get(name, "core", "anonymous_login")));
+ if not (config.get(name, "core", "anonymous_login") or
+ config.get(name, "core", "authentication") == "anonymous") then
+ srvname(san, name, "xmpp-server");
+ end
+ xmppAddr(san, name);
+ end
+ end
+ end
+
+ for t, n in pairs(san) do
+ for i = 1,#n do
+ t_insert(sslsanconf, s_format("%s.%d = %s", t, i -1, n[i]));
+ end
+ end
+
+ return raw and sslsanconf or t_concat(sslsanconf, "\n");
+end
+
+function baseconf()
+ return {
+ req = {
+ distinguished_name = "distinguished_name",
+ req_extensions = "v3_extensions",
+ x509_extensions = "v3_extensions",
+ prompt = "no",
+ },
+ distinguished_name = {
+ commonName = "example.com",
+ countryName = "GB",
+ localityName = "The Internet",
+ organizationName = "Your Organisation",
+ organizationalUnitName = "XMPP Department",
+ emailAddress = "xmpp@example.com",
+ },
+ v3_extensions = {
+ basicConstraints = "CA:FALSE",
+ keyUsage = "digitalSignature,keyEncipherment",
+ extendedKeyUsage = "serverAuth,clientAuth",
+ subjectAltName = "@subject_alternative_name",
+ },
+ subject_alternative_name = { },
+ }
+end
+
+function serialize_conf(conf)
+ local s = "";
+ for k, t in pairs(conf) do
+ s = s .. ("[%s]\n"):format(k);
+ if t[1] then
+ for i, v in ipairs(t) do
+ s = s .. ("%s\n"):format(v);
+ end
+ else
+ for k, v in pairs(t) do
+ s = s .. ("%s = %s\n"):format(k, v);
+ end
+ end
+ s = s .. "\n";
+ end
+ return s;
+end
+
return _M;
diff --git a/util/xmppstream.lua b/util/xmppstream.lua
index e5271b72..f1793b4f 100644
--- a/util/xmppstream.lua
+++ b/util/xmppstream.lua
@@ -11,32 +11,25 @@ local lxp = require "lxp";
local st = require "util.stanza";
local stanza_mt = st.stanza_mt;
+local error = error;
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");
-
-- COMPAT: w/LuaExpat 1.1.0
local lxp_supports_doctype = pcall(lxp.new, { StartDoctypeDecl = false });
-if not lxp_supports_doctype then
- default_log("warn", "The version of LuaExpat on your system leaves Prosody "
- .."vulnerable to denial-of-service attacks. You should upgrade to "
- .."LuaExpat 1.1.1 or higher as soon as possible. See "
- .."http://prosody.im/doc/depends#luaexpat for more information.");
-end
-
-local error = error;
-
module "xmppstream"
local new_parser = lxp.new;
-local ns_prefixes = {
- ["http://www.w3.org/XML/1998/namespace"] = "xml";
+local xml_namespace = {
+ ["http://www.w3.org/XML/1998/namespace\1lang"] = "xml:lang";
+ ["http://www.w3.org/XML/1998/namespace\1space"] = "xml:space";
+ ["http://www.w3.org/XML/1998/namespace\1base"] = "xml:base";
+ ["http://www.w3.org/XML/1998/namespace\1id"] = "xml:id";
};
local xmlns_streams = "http://etherx.jabber.org/streams";
@@ -50,8 +43,6 @@ _M.ns_pattern = ns_pattern;
function new_sax_handlers(session, stream_callbacks)
local xml_handlers = {};
- local log = session.log or default_log;
-
local cb_streamopened = stream_callbacks.streamopened;
local cb_streamclosed = stream_callbacks.streamclosed;
local cb_error = stream_callbacks.error or function(session, e) error("XML stream error: "..tostring(e)); end;
@@ -85,17 +76,13 @@ function new_sax_handlers(session, stream_callbacks)
non_streamns_depth = non_streamns_depth + 1;
end
- -- FIXME !!!!!
for i=1,#attr do
local k = attr[i];
attr[i] = nil;
- local ns, nm = k:match(ns_pattern);
- if nm ~= "" then
- ns = ns_prefixes[ns];
- if ns then
- attr[ns..":"..nm] = attr[k];
- attr[k] = nil;
- end
+ local xmlk = xml_namespace[k];
+ if xmlk then
+ attr[xmlk] = attr[k];
+ attr[k] = nil;
end
end
@@ -152,19 +139,9 @@ function new_sax_handlers(session, stream_callbacks)
stanza = t_remove(stack);
end
else
- if tagname == stream_tag then
- if cb_streamclosed then
- cb_streamclosed(session);
- end
- else
- local curr_ns,name = tagname:match(ns_pattern);
- if name == "" then
- curr_ns, name = "", curr_ns;
- end
- cb_error(session, "parse-error", "unexpected-element-close", name);
+ if cb_streamclosed then
+ cb_streamclosed(session);
end
- stanza, chardata = nil, {};
- stack = {};
end
end
@@ -188,7 +165,6 @@ function new_sax_handlers(session, stream_callbacks)
local function set_session(stream, new_session)
session = new_session;
- log = new_session.log or default_log;
end
return xml_handlers, { reset = reset, set_session = set_session };