Changeset

6254:da4c04df90e3

Merge with daurnimator
author Matthew Wild <mwild1@gmail.com>
date Fri, 23 May 2014 20:37:16 +0100
parents 6233:f400a4cdf352 (diff) 6253:2df9e7a67e56 (current diff)
children 6258:8a01bce29834
files plugins/muc/muc.lib.lua
diffstat 44 files changed, 1240 insertions(+), 322 deletions(-) [+]
line wrap: on
line diff
--- a/.hgtags	Wed May 21 13:19:57 2014 -0400
+++ b/.hgtags	Fri May 23 20:37:16 2014 +0100
@@ -51,3 +51,4 @@
 49e3c49eb0d8f33e94e2bf37e5421deacac5f499 0.9.2
 56b1f151f4a31fcc7dbde49e064a288715077ece 0.9.2
 872ff4851c9b6cd662aac4b1a056ac2a97c85ce5 0.9.3
+5d73412aa1ba39081683ab922575eae93e4e867a 0.9.4
--- a/Makefile	Wed May 21 13:19:57 2014 -0400
+++ b/Makefile	Fri May 23 20:37:16 2014 +0100
@@ -55,7 +55,7 @@
 	$(MAKE) install -C util-src
 
 %.install: %
-	sed "1s/\blua\b/$(RUNWITH)/; \
+	sed "1s| lua$$| $(RUNWITH)|; \
 		s|^CFG_SOURCEDIR=.*;$$|CFG_SOURCEDIR='$(INSTALLEDSOURCE)';|; \
 		s|^CFG_CONFIGDIR=.*;$$|CFG_CONFIGDIR='$(INSTALLEDCONFIG)';|; \
 		s|^CFG_DATADIR=.*;$$|CFG_DATADIR='$(INSTALLEDDATA)';|; \
--- a/core/certmanager.lua	Wed May 21 13:19:57 2014 -0400
+++ b/core/certmanager.lua	Fri May 23 20:37:16 2014 +0100
@@ -15,9 +15,11 @@
 local pairs = pairs;
 local type = type;
 local io_open = io.open;
+local t_concat = table.concat;
+local t_insert = table.insert;
 
 local prosody = prosody;
-local resolve_path = configmanager.resolve_relative_path;
+local resolve_path = require"util.paths".resolve_relative_path;
 local config_path = prosody.paths.config;
 
 local luasec_has_noticket, luasec_has_verifyext, luasec_has_no_compression;
@@ -33,11 +35,19 @@
 -- Global SSL options if not overridden per-host
 local global_ssl_config = configmanager.get("*", "ssl");
 
+-- Built-in defaults
 local core_defaults = {
 	capath = "/etc/ssl/certs";
-	protocol = "sslv23";
+	protocol = "tlsv1+";
 	verify = (ssl and ssl.x509 and { "peer", "client_once", }) or "none";
-	options = { "no_sslv2", "no_sslv3", "cipher_server_preference", luasec_has_noticket and "no_ticket" or nil };
+	options = {
+		cipher_server_preference = true;
+		no_ticket = luasec_has_noticket;
+		no_compression = luasec_has_no_compression and configmanager.get("*", "ssl_compression") ~= true;
+		-- Has no_compression? Then it has these too...
+		single_dh_use = luasec_has_no_compression;
+		single_ecdh_use = luasec_has_no_compression;
+	};
 	verifyext = { "lsec_continue", "lsec_ignore_purpose" };
 	curve = "secp384r1";
 	ciphers = "HIGH+kEDH:HIGH+kEECDH:HIGH:!PSK:!SRP:!3DES:!aNULL";
@@ -45,6 +55,9 @@
 local path_options = { -- These we pass through resolve_path()
 	key = true, certificate = true, cafile = true, capath = true, dhparam = true
 }
+local set_options = {
+	options = true, verify = true, verifyext = true
+}
 
 if ssl and not luasec_has_verifyext and ssl.x509 then
 	-- COMPAT mw/luasec-hg
@@ -53,14 +66,21 @@
 	end
 end
 
-if luasec_has_no_compression then -- Has no_compression? Then it has these too...
-	core_defaults.options[#core_defaults.options+1] = "single_dh_use";
-	core_defaults.options[#core_defaults.options+1] = "single_ecdh_use";
-	if configmanager.get("*", "ssl_compression") ~= true then
-		core_defaults.options[#core_defaults.options+1] = "no_compression";
+local function merge_set(t, o)
+	if type(t) ~= "table" then t = { t } end
+	for k,v in pairs(t) do
+		if v == true or v == false then
+			o[k] = v;
+		else
+			o[v] = true;
+		end
 	end
+	return o;
 end
 
+local protocols = { "sslv2", "sslv3", "tlsv1", "tlsv1_1", "tlsv1_2" };
+for i = 1, #protocols do protocols[protocols[i] .. "+"] = i - 1; end
+
 function create_context(host, mode, user_ssl_config)
 	user_ssl_config = user_ssl_config or {}
 	user_ssl_config.mode = mode;
@@ -69,25 +89,61 @@
 
 	if global_ssl_config then
 		for option,default_value in pairs(global_ssl_config) do
-			if not user_ssl_config[option] then
+			if user_ssl_config[option] == nil then
 				user_ssl_config[option] = default_value;
 			end
 		end
 	end
+
 	for option,default_value in pairs(core_defaults) do
-		if not user_ssl_config[option] then
+		if user_ssl_config[option] == nil then
 			user_ssl_config[option] = default_value;
 		end
 	end
-	user_ssl_config.password = user_ssl_config.password or function() log("error", "Encrypted certificate for %s requires 'ssl' 'password' to be set in config", host); end;
+
+	for option in pairs(set_options) do
+		local merged = {};
+		merge_set(core_defaults[option], merged);
+		if global_ssl_config then
+			merge_set(global_ssl_config[option], merged);
+		end
+		merge_set(user_ssl_config[option], merged);
+		local final_array = {};
+		for opt, enable in pairs(merged) do
+			if enable then
+				final_array[#final_array+1] = opt;
+			end
+		end
+		user_ssl_config[option] = final_array;
+	end
+
+	local min_protocol = protocols[user_ssl_config.protocol];
+	if min_protocol then
+		user_ssl_config.protocol = "sslv23";
+		for i = 1, min_protocol do
+			t_insert(user_ssl_config.options, "no_"..protocols[i]);
+		end
+	end
+
+	-- We can't read the password interactively when daemonized
+	user_ssl_config.password = user_ssl_config.password or
+		function() log("error", "Encrypted certificate for %s requires 'ssl' 'password' to be set in config", host); end;
+
 	for option in pairs(path_options) do
 		if type(user_ssl_config[option]) == "string" then
 			user_ssl_config[option] = resolve_path(config_path, user_ssl_config[option]);
 		end
 	end
 
-	if not user_ssl_config.key then return nil, "No key present in SSL/TLS configuration for "..host; end
-	if not user_ssl_config.certificate then return nil, "No certificate present in SSL/TLS configuration for "..host; end
+	-- Allow the cipher list to be a table
+	if type(user_ssl_config.ciphers) == "table" then
+		user_ssl_config.ciphers = t_concat(user_ssl_config.ciphers, ":")
+	end
+
+	if mode == "server" then
+		if not user_ssl_config.key then return nil, "No key present in SSL/TLS configuration for "..host; end
+		if not user_ssl_config.certificate then return nil, "No certificate present in SSL/TLS configuration for "..host; end
+	end
 
 	-- LuaSec expects dhparam to be a callback that takes two arguments.
 	-- We ignore those because it is mostly used for having a separate
@@ -141,6 +197,9 @@
 
 function reload_ssl_config()
 	global_ssl_config = configmanager.get("*", "ssl");
+	if luasec_has_no_compression then
+		core_defaults.options.no_compression = configmanager.get("*", "ssl_compression") ~= true;
+	end
 end
 
 prosody.events.add_handler("config-reloaded", reload_ssl_config);
--- a/core/configmanager.lua	Wed May 21 13:19:57 2014 -0400
+++ b/core/configmanager.lua	Fri May 23 20:37:16 2014 +0100
@@ -14,11 +14,15 @@
 local fire_event = prosody and prosody.events.fire_event or function () end;
 
 local envload = require"util.envload".envload;
-local lfs = require "lfs";
+local deps = require"util.dependencies";
+local resolve_relative_path = require"util.paths".resolve_relative_path;
+local glob_to_pattern = require"util.paths".glob_to_pattern;
 local path_sep = package.config:sub(1,1);
 
 module "configmanager"
 
+_M.resolve_relative_path = resolve_relative_path; -- COMPAT
+
 local parsers = {};
 
 local config_mt = { __index = function (t, k) return rawget(t, "*"); end};
@@ -66,41 +70,6 @@
 	return set(config, host, key, value);
 end
 
--- Helper function to resolve relative paths (needed by config)
-do
-	function resolve_relative_path(parent_path, path)
-		if path then
-			-- Some normalization
-			parent_path = parent_path:gsub("%"..path_sep.."+$", "");
-			path = path:gsub("^%.%"..path_sep.."+", "");
-
-			local is_relative;
-			if path_sep == "/" and path:sub(1,1) ~= "/" then
-				is_relative = true;
-			elseif path_sep == "\\" and (path:sub(1,1) ~= "/" and (path:sub(2,3) ~= ":\\" and path:sub(2,3) ~= ":/")) then
-				is_relative = true;
-			end
-			if is_relative then
-				return parent_path..path_sep..path;
-			end
-		end
-		return path;
-	end
-end
-
--- Helper function to convert a glob to a Lua pattern
-local function glob_to_pattern(glob)
-	return "^"..glob:gsub("[%p*?]", function (c)
-		if c == "*" then
-			return ".*";
-		elseif c == "?" then
-			return ".";
-		else
-			return "%"..c;
-		end
-	end).."$";
-end
-
 function load(filename, format)
 	format = format or filename:match("%w+$");
 
@@ -214,6 +183,10 @@
 
 		function env.Include(file)
 			if file:match("[*?]") then
+				local lfs = deps.softreq "lfs";
+				if not lfs then
+					error(format("Error expanding wildcard pattern in Include %q - LuaFileSystem not available", file));
+				end
 				local path_pos, glob = file:match("()([^"..path_sep.."]+)$");
 				local path = file:sub(1, math_max(path_pos-2,0));
 				local config_path = config_file:gsub("[^"..path_sep.."]+$", "");
--- a/core/moduleapi.lua	Wed May 21 13:19:57 2014 -0400
+++ b/core/moduleapi.lua	Fri May 23 20:37:16 2014 +0100
@@ -13,11 +13,14 @@
 local logger = require "util.logger";
 local pluginloader = require "util.pluginloader";
 local timer = require "util.timer";
+local resolve_relative_path = require"util.paths".resolve_relative_path;
 
 local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat;
 local error, setmetatable, type = error, setmetatable, type;
-local ipairs, pairs, select, unpack = ipairs, pairs, select, unpack;
+local ipairs, pairs, select = ipairs, pairs, select;
 local tonumber, tostring = tonumber, tostring;
+local pack = table.pack or function(...) return {n=select("#",...), ...}; end -- table.pack is only in 5.2
+local unpack = table.unpack or unpack; -- renamed in 5.2
 
 local prosody = prosody;
 local hosts = prosody.hosts;
@@ -347,11 +350,29 @@
 	return core_post_stanza(hosts[self.host], stanza);
 end
 
-function api:add_timer(delay, callback)
-	return timer.add_task(delay, function (t)
-		if self.loaded == false then return; end
-		return callback(t);
-	end);
+local timer_methods = { }
+local timer_mt = {
+	__index = timer_methods;
+}
+function timer_methods:stop( )
+	timer.stop(self.id);
+end
+timer_methods.disarm = timer_methods.stop
+function timer_methods:reschedule(delay)
+	timer.reschedule(self.id, delay)
+end
+
+local function timer_callback(now, id, t)
+	if t.module_env.loaded == false then return; end
+	return t.callback(now, unpack(t, 1, t.n));
+end
+
+function api:add_timer(delay, callback, ...)
+	local t = pack(...)
+	t.module_env = self;
+	t.callback = callback;
+	t.id = timer.add_task(delay, timer_callback, t);
+	return setmetatable(t, timer_mt);
 end
 
 local path_sep = package.config:sub(1,1);
@@ -360,7 +381,7 @@
 end
 
 function api:load_resource(path, mode)
-	path = config.resolve_relative_path(self:get_directory(), path);
+	path = resolve_relative_path(self:get_directory(), path);
 	return io.open(path, mode);
 end
 
--- a/core/modulemanager.lua	Wed May 21 13:19:57 2014 -0400
+++ b/core/modulemanager.lua	Fri May 23 20:37:16 2014 +0100
@@ -30,7 +30,7 @@
 end
 
 local autoload_modules = {prosody.platform, "presence", "message", "iq", "offline", "c2s", "s2s"};
-local component_inheritable_modules = {"tls", "dialback", "iq", "s2s"};
+local component_inheritable_modules = {"tls", "saslauth", "dialback", "iq", "s2s"};
 
 -- We need this to let modules access the real global namespace
 local _G = _G;
--- a/core/portmanager.lua	Wed May 21 13:19:57 2014 -0400
+++ b/core/portmanager.lua	Fri May 23 20:37:16 2014 +0100
@@ -29,6 +29,8 @@
 	table.insert(default_local_interfaces, "::1");
 end
 
+local default_mode = config.get("*", "network_default_read_size") or 4096;
+
 --- Private state
 
 -- service_name -> { service_info, ... }
@@ -111,7 +113,7 @@
 		   }
 	bind_ports = set.new(type(bind_ports) ~= "table" and { bind_ports } or bind_ports );
 
-	local mode, ssl = listener.default_mode or "*a";
+	local mode, ssl = listener.default_mode or default_mode;
 	local hooked_ports = {};
 
 	for interface in bind_interfaces do
--- a/net/http.lua	Wed May 21 13:19:57 2014 -0400
+++ b/net/http.lua	Fri May 23 20:37:16 2014 +0100
@@ -6,7 +6,6 @@
 -- COPYING file in the source package for more information.
 --
 
-local socket = require "socket"
 local b64 = require "util.encodings".base64.encode;
 local url = require "socket.url"
 local httpstream_new = require "net.http.parser".new;
@@ -160,21 +159,17 @@
 	end
 	local port_number = port and tonumber(port) or (using_https and 443 or 80);
 
-	-- Connect the socket, and wrap it with net.server
-	local conn = socket.tcp();
-	conn:settimeout(10);
-	local ok, err = conn:connect(host, port_number);
-	if not ok and err ~= "timeout" then
-		callback(nil, 0, req);
-		return nil, err;
-	end
-
 	local sslctx = false;
 	if using_https then
 		sslctx = ex and ex.sslctx or { mode = "client", protocol = "sslv23", options = { "no_sslv2" } };
 	end
 
-	req.handler, req.conn = assert(server.wrapclient(conn, host, port_number, listener, "*a", sslctx));
+	local handler, conn = server.addclient(host, port_number, listener, "*a", sslctx)
+	if not handler then
+		callback(nil, 0, req);
+		return nil, conn;
+	end
+	req.handler, req.conn = handler, conn
 	req.write = function (...) return req.handler:write(...); end
 
 	req.callback = function (content, code, request, response) log("debug", "Calling callback, status %s", code or "---"); return select(2, xpcall(function () return callback(content, code, request, response) end, handleerr)); end
--- a/net/http/server.lua	Wed May 21 13:19:57 2014 -0400
+++ b/net/http/server.lua	Fri May 23 20:37:16 2014 +0100
@@ -185,6 +185,7 @@
 		persistent = persistent;
 		conn = conn;
 		send = _M.send_response;
+		done = _M.finish_response;
 		finish_cb = finish_cb;
 	};
 	conn._http_open_response = response;
@@ -246,24 +247,30 @@
 	response.status_code = 404;
 	response:send(events.fire_event("http-error", { code = 404 }));
 end
-function _M.send_response(response, body)
-	if response.finished then return; end
-	response.finished = true;
-	response.conn._http_open_response = nil;
-
+local function prepare_header(response)
 	local status_line = "HTTP/"..response.request.httpversion.." "..(response.status or codes[response.status_code]);
 	local headers = response.headers;
-	body = body or response.body or "";
-	headers.content_length = #body;
-
 	local output = { status_line };
 	for k,v in pairs(headers) do
 		t_insert(output, headerfix[k]..v);
 	end
 	t_insert(output, "\r\n\r\n");
+	return output;
+end
+_M.prepare_header = prepare_header;
+function _M.send_response(response, body)
+	if response.finished then return; end
+	body = body or response.body or "";
+	response.headers.content_length = #body;
+	local output = prepare_header(response);
 	t_insert(output, body);
-
 	response.conn:write(t_concat(output));
+	response:done();
+end
+function _M.finish_response(response)
+	if response.finished then return; end
+	response.finished = true;
+	response.conn._http_open_response = nil;
 	if response.on_destroy then
 		response:on_destroy();
 		response.on_destroy = nil;
--- a/net/server_event.lua	Wed May 21 13:19:57 2014 -0400
+++ b/net/server_event.lua	Fri May 23 20:37:16 2014 +0100
@@ -44,8 +44,9 @@
 local t_insert = table.insert
 local t_concat = table.concat
 
-local ssl = use "ssl"
+local has_luasec, ssl = pcall ( require , "ssl" )
 local socket = use "socket" or require "socket"
+local getaddrinfo = socket.dns.getaddrinfo
 
 local log = require ("util.logger").init("socket")
 
@@ -128,7 +129,7 @@
 		return self:_destroy();
 	end
 
-	function interface_mt:_start_connection(plainssl) -- should be called from addclient
+	function interface_mt:_start_connection(plainssl) -- called from wrapclient
 			local callback = function( event )
 				if EV_TIMEOUT == event then  -- timeout during connection
 					self.fatalerror = "connection timeout"
@@ -136,7 +137,7 @@
 					self:_close()
 					debug( "new connection failed. id:", self.id, "error:", self.fatalerror )
 				else
-					if plainssl and ssl then  -- start ssl session
+					if plainssl and has_luasec then  -- start ssl session
 						self:starttls(self._sslctx, true)
 					else  -- normal connection
 						self:_start_session(true)
@@ -367,6 +368,7 @@
 	function interface_mt:ssl()
 		return self._usingssl
 	end
+	interface_mt.clientport = interface_mt.port -- COMPAT server_select
 
 	function interface_mt:type()
 		return self._type or "client"
@@ -506,7 +508,7 @@
 			_sslctx = sslctx; -- parameters
 			_usingssl = false;  -- client is using ssl;
 		}
-		if not ssl then interface.starttls = false; end
+		if not has_luasec then interface.starttls = false; end
 		interface.id = tostring(interface):match("%x+$");
 		interface.writecallback = function( event )  -- called on write events
 			--vdebug( "new client write event, id/ip/port:", interface, ip, port )
@@ -689,7 +691,7 @@
 				interface._connections = interface._connections + 1  -- increase connection count
 				local clientinterface = handleclient( client, client_ip, client_port, interface, pattern, listener, sslctx )
 				--vdebug( "client id:", clientinterface, "startssl:", startssl )
-				if ssl and sslctx then
+				if has_luasec and sslctx then
 					clientinterface:starttls(sslctx, true)
 				else
 					clientinterface:_start_session( true )
@@ -710,25 +712,17 @@
 end
 
 local addserver = ( function( )
-	return function( addr, port, listener, pattern, sslcfg, startssl )  -- TODO: check arguments
-		--vdebug( "creating new tcp server with following parameters:", addr or "nil", port or "nil", sslcfg or "nil", startssl or "nil")
+	return function( addr, port, listener, pattern, sslctx, startssl )  -- TODO: check arguments
+		--vdebug( "creating new tcp server with following parameters:", addr or "nil", port or "nil", sslctx or "nil", startssl or "nil")
+		if sslctx and not has_luasec then
+			debug "fatal error: luasec not found"
+			return nil, "luasec not found"
+		end
 		local server, err = socket.bind( addr, port, cfg.ACCEPT_QUEUE )  -- create server socket
 		if not server then
 			debug( "creating server socket on "..addr.." port "..port.." failed:", err )
 			return nil, err
 		end
-		local sslctx
-		if sslcfg then
-			if not ssl then
-				debug "fatal error: luasec not found"
-				return nil, "luasec not found"
-			end
-			sslctx, err = sslcfg
-			if err then
-				debug( "error while creating new ssl context for server socket:", err )
-				return nil, err
-			end
-		end
 		local interface = handleserver( server, addr, port, pattern, listener, sslctx, startssl )  -- new server handler
 		debug( "new server created with id:", tostring(interface))
 		return interface
@@ -744,37 +738,34 @@
 		--function handleclient( client, ip, port, server, pattern, listener, _, sslctx )  -- creates an client interface
 	end
 
-	function addclient( addr, serverport, listener, pattern, localaddr, localport, sslcfg, startssl )
-		local client, err = socket.tcp()  -- creating new socket
+	function addclient( addr, serverport, listener, pattern, sslctx, typ )
+		if sslctx and not has_luasec then
+			debug "need luasec, but not available"
+			return nil, "luasec not found"
+		end
+		if getaddrinfo and not typ then
+			local addrinfo, err = getaddrinfo(addr)
+			if not addrinfo then return nil, err end
+			if addrinfo[1] and addrinfo[1].family == "inet6" then
+				typ = "tcp6"
+			end
+		end
+		local create = socket[typ or "tcp"]
+		if type( create ) ~= "function"  then
+			return nil, "invalid socket type"
+		end
+		local client, err = create()  -- creating new socket
 		if not client then
 			debug( "cannot create socket:", err )
 			return nil, err
 		end
 		client:settimeout( 0 )  -- set nonblocking
-		if localaddr then
-			local res, err = client:bind( localaddr, localport, -1 )
-			if not res then
-				debug( "cannot bind client:", err )
-				return nil, err
-			end
-		end
-		local sslctx
-		if sslcfg then  -- handle ssl/new context
-			if not ssl then
-				debug "need luasec, but not available"
-				return nil, "luasec not found"
-			end
-			sslctx, err = sslcfg
-			if err then
-				debug( "cannot create new ssl context:", err )
-				return nil, err
-			end
-		end
 		local res, err = client:connect( addr, serverport )  -- connect
 		if res or ( err == "timeout" ) then
-			local ip, port = client:getsockname( )
-			local interface = wrapclient( client, ip, serverport, listener, pattern, sslctx, startssl )
-			interface:_start_connection( startssl )
+			if client.getsockname then
+				addr = client:getsockname( )
+			end
+			local interface = wrapclient( client, addr, serverport, listener, pattern, sslctx )
 			debug( "new connection id:", interface.id )
 			return interface, err
 		else
--- a/net/server_select.lua	Wed May 21 13:19:57 2014 -0400
+++ b/net/server_select.lua	Fri May 23 20:37:16 2014 +0100
@@ -48,13 +48,14 @@
 
 --// extern libs //--
 
-local luasec = use "ssl"
+local has_luasec, luasec = pcall ( require , "ssl" )
 local luasocket = use "socket" or require "socket"
 local luasocket_gettime = luasocket.gettime
+local getaddrinfo = luasocket.dns.getaddrinfo
 
 --// extern lib methods //--
 
-local ssl_wrap = ( luasec and luasec.wrap )
+local ssl_wrap = ( has_luasec and luasec.wrap )
 local socket_bind = luasocket.bind
 local socket_sleep = luasocket.sleep
 local socket_select = luasocket.select
@@ -401,6 +402,7 @@
 	handler.clientport = function( )
 		return clientport
 	end
+	handler.port = handler.clientport -- COMPAT server_event
 	local write = function( self, data )
 		bufferlen = bufferlen + #data
 		if bufferlen > maxsendlen then
@@ -585,7 +587,7 @@
 			end
 		)
 	end
-	if luasec then
+	if has_luasec then
 		handler.starttls = function( self, _sslctx)
 			if _sslctx then
 				handler:set_sslctx(_sslctx);
@@ -638,7 +640,7 @@
 	_socketlist[ socket ] = handler
 	_readlistlen = addsocket(_readlist, socket, _readlistlen)
 
-	if sslctx and luasec then
+	if sslctx and has_luasec then
 		out_put "server.lua: auto-starting ssl negotiation..."
 		handler.autostart_ssl = true;
 		local ok, err = handler:starttls(sslctx);
@@ -713,22 +715,23 @@
 ----------------------------------// PUBLIC //--
 
 addserver = function( addr, port, listeners, pattern, sslctx ) -- this function provides a way for other scripts to reg a server
+	addr = addr or "*"
 	local err
 	if type( listeners ) ~= "table" then
 		err = "invalid listener table"
-	end
-	if type( port ) ~= "number" or not ( port >= 0 and port <= 65535 ) then
+	elseif type ( addr ) ~= "string" then
+		err = "invalid address"
+	elseif type( port ) ~= "number" or not ( port >= 0 and port <= 65535 ) then
 		err = "invalid port"
 	elseif _server[ addr..":"..port ] then
 		err = "listeners on '[" .. addr .. "]:" .. port .. "' already exist"
-	elseif sslctx and not luasec then
+	elseif sslctx and not has_luasec then
 		err = "luasec not found"
 	end
 	if err then
 		out_error( "server.lua, [", addr, "]:", port, ": ", err )
 		return nil, err
 	end
-	addr = addr or "*"
 	local server, err = socket_bind( addr, port, _tcpbacklog )
 	if err then
 		out_error( "server.lua, [", addr, "]:", port, ": ", err )
@@ -929,17 +932,44 @@
 	return handler, socket
 end
 
-local addclient = function( address, port, listeners, pattern, sslctx )
-	local client, err = luasocket.tcp( )
+local addclient = function( address, port, listeners, pattern, sslctx, typ )
+	local err
+	if type( listeners ) ~= "table" then
+		err = "invalid listener table"
+	elseif type ( address ) ~= "string" then
+		err = "invalid address"
+	elseif type( port ) ~= "number" or not ( port >= 0 and port <= 65535 ) then
+		err = "invalid port"
+	elseif sslctx and not has_luasec then
+		err = "luasec not found"
+	end
+	if getaddrinfo and not typ then
+		local addrinfo, err = getaddrinfo(address)
+		if not addrinfo then return nil, err end
+		if addrinfo[1] and addrinfo[1].family == "inet6" then
+			typ = "tcp6"
+		end
+	end
+	local create = luasocket[typ or "tcp"]
+	if type( create ) ~= "function"  then
+		err = "invalid socket type"
+	end
+
+	if err then
+		out_error( "server.lua, addclient: ", err )
+		return nil, err
+	end
+
+	local client, err = create( )
 	if err then
 		return nil, err
 	end
 	client:settimeout( 0 )
-	_, err = client:connect( address, port )
-	if err then -- try again
+	local ok, err = client:connect( address, port )
+	if ok or err == "timeout" then
 		return wrapclient( client, address, port, listeners, pattern, sslctx )
 	else
-		return wrapconnection( nil, listeners, client, address, port, "clientport", pattern, sslctx )
+		return nil, err
 	end
 end
 
--- a/plugins/mod_admin_telnet.lua	Wed May 21 13:19:57 2014 -0400
+++ b/plugins/mod_admin_telnet.lua	Fri May 23 20:37:16 2014 +0100
@@ -154,6 +154,14 @@
 	session.partial_data = data:match("[^\n]+$");
 end
 
+function console_listener.onreadtimeout(conn)
+	local session = sessions[conn];
+	if session then
+		session.send("\0");
+		return true;
+	end
+end
+
 function console_listener.ondisconnect(conn, err)
 	local session = sessions[conn];
 	if session then
@@ -212,9 +220,11 @@
 		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:show_tls() - Show TLS cipher info for encrypted sessions]]
 		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:show_tls(domain) - Show TLS cipher info for encrypted sessions]]
 		print [[s2s:close(from, to) - Close a connection from one domain to another]]
 		print [[s2s:closeall(host) - Close all the incoming/outgoing s2s sessions to specified host]]
 	elseif section == "module" then
@@ -471,22 +481,28 @@
 	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);
+local function common_info(session, line)
+	if session.id then
+		line[#line+1] = "["..session.id.."]"
+	else
+		line[#line+1] = "["..session.type..(tostring(session):match("%x*$")).."]"
 	end
-	return true, "Done";
-end
-
-function def_env.hosts:add(name)
 end
 
 local function session_flags(session, line)
 	line = line or {};
+	common_info(session, line);
+	if session.type == "c2s" then
+		local status, priority = "unavailable", tostring(session.priority or "-");
+		if session.presence then
+			status = session.presence:get_child_text("show") or "available";
+		end
+		line[#line+1] = status.."("..priority..")";
+	end
 	if session.cert_identity_status == "valid" then
-		line[#line+1] = "(secure)";
-	elseif session.secure then
+		line[#line+1] = "(authenticated)";
+	end
+	if session.secure then
 		line[#line+1] = "(encrypted)";
 	end
 	if session.compressed then
@@ -501,6 +517,23 @@
 	return table.concat(line, " ");
 end
 
+local function tls_info(session, line)
+	line = line or {};
+	common_info(session, line);
+	if session.secure then
+		local sock = session.conn and session.conn.socket and session.conn:socket();
+		if sock and sock.info then
+			local info = sock:info();
+			line[#line+1] = ("(%s with %s)"):format(info.protocol, info.cipher);
+		else
+			line[#line+1] = "(cipher info unavailable)";
+		end
+	else
+		line[#line+1] = "(insecure)";
+	end
+	return table.concat(line, " ");
+end
+
 def_env.c2s = {};
 
 local function show_c2s(callback)
@@ -524,8 +557,9 @@
 	return true, "Total: "..count.." clients";
 end
 
-function def_env.c2s:show(match_jid)
+function def_env.c2s:show(match_jid, annotate)
 	local print, count = self.session.print, 0;
+	annotate = annotate or session_flags;
 	local curr_host;
 	show_c2s(function (jid, session)
 		if curr_host ~= session.host then
@@ -534,11 +568,7 @@
 		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:get_child_text("show") or "available";
-			end
-			print(session_flags(session, { "   "..jid.." - "..status.."("..priority..")" }));
+			print(annotate(session, { "  ", jid }));
 		end
 	end);
 	return true, "Total: "..count.." clients";
@@ -566,6 +596,10 @@
 	return true, "Total: "..count.." secure client connections";
 end
 
+function def_env.c2s:show_tls(match_jid)
+	return self:show(match_jid, tls_info);
+end
+
 function def_env.c2s:close(match_jid)
 	local count = 0;
 	show_c2s(function (jid, session)
@@ -579,8 +613,9 @@
 
 
 def_env.s2s = {};
-function def_env.s2s:show(match_jid)
+function def_env.s2s:show(match_jid, annotate)
 	local print = self.session.print;
+	annotate = annotate or session_flags;
 
 	local count_in, count_out = 0,0;
 	local s2s_list = { };
@@ -598,8 +633,7 @@
 			remotehost, localhost = session.from_host or "?", session.to_host or "?";
 		end
 		local sess_lines = { l = localhost, r = remotehost,
-			session_flags(session, { "", direction, remotehost or "?",
-				"["..session.type..tostring(session):match("[a-f0-9]*$").."]" })};
+			annotate(session, { "", direction, remotehost or "?" })};
 
 		if (not match_jid) or remotehost:match(match_jid) or localhost:match(match_jid) then
 			table.insert(s2s_list, sess_lines);
@@ -654,6 +688,10 @@
 	return true, "Total: "..count_out.." outgoing, "..count_in.." incoming connections";
 end
 
+function def_env.s2s:show_tls(match_jid)
+	return self:show(match_jid, tls_info);
+end
+
 local function print_subject(print, subject)
 	for _, entry in ipairs(subject) do
 		print(
@@ -823,9 +861,19 @@
 function def_env.host:list()
 	local print = self.session.print;
 	local i = 0;
+	local type;
 	for host in values(array.collect(keys(prosody.hosts)):sort()) do
 		i = i + 1;
-		print(host);
+		type = hosts[host].type;
+		if type == "local" then
+			print(host);
+		else
+			type = module:context(host):get_option_string("component_module", type);
+			if type ~= "component" then
+				type = type .. " component";
+			end
+			print(("%s (%s)"):format(host, type));
+		end
 	end
 	return true, i.." hosts";
 end
@@ -896,6 +944,9 @@
 
 function def_env.muc:create(room_jid)
 	local room, host = check_muc(room_jid);
+	if not room_name then
+		return room_name, host;
+	end
 	if not room then return nil, host end
 	if hosts[host].modules.muc.rooms[room_jid] then return nil, "Room exists already" end
 	return hosts[host].modules.muc.create_room(room_jid);
@@ -903,6 +954,9 @@
 
 function def_env.muc:room(room_jid)
 	local room_name, host = check_muc(room_jid);
+	if not room_name then
+		return room_name, host;
+	end
 	local room_obj = hosts[host].modules.muc.rooms[room_jid];
 	if not room_obj then
 		return nil, "No such room: "..room_jid;
--- a/plugins/mod_auth_anonymous.lua	Wed May 21 13:19:57 2014 -0400
+++ b/plugins/mod_auth_anonymous.lua	Fri May 23 20:37:16 2014 +0100
@@ -43,7 +43,7 @@
 end
 
 function provider.users()
-	return next, hosts[host].sessions, nil;
+	return next, hosts[module.host].sessions, nil;
 end
 
 -- datamanager callback to disable writes
--- a/plugins/mod_auth_internal_hashed.lua	Wed May 21 13:19:57 2014 -0400
+++ b/plugins/mod_auth_internal_hashed.lua	Fri May 23 20:37:16 2014 +0100
@@ -7,6 +7,8 @@
 -- COPYING file in the source package for more information.
 --
 
+local max = math.max;
+
 local getAuthenticationDatabaseSHA1 = require "util.sasl.scram".getAuthenticationDatabaseSHA1;
 local usermanager = require "core.usermanager";
 local generate_uuid = require "util.uuid".generate;
@@ -39,7 +41,7 @@
 
 
 -- Default; can be set per-user
-local iteration_count = 4096;
+local default_iteration_count = 4096;
 
 -- define auth provider
 local provider = {};
@@ -80,8 +82,8 @@
 	log("debug", "set_password for username '%s'", username);
 	local account = accounts:get(username);
 	if account then
-		account.salt = account.salt or generate_uuid();
-		account.iteration_count = account.iteration_count or iteration_count;
+		account.salt = generate_uuid();
+		account.iteration_count = max(account.iteration_count or 0, default_iteration_count);
 		local valid, stored_key, server_key = getAuthenticationDatabaseSHA1(password, account.salt, account.iteration_count);
 		local stored_key_hex = to_hex(stored_key);
 		local server_key_hex = to_hex(server_key);
@@ -113,10 +115,10 @@
 		return accounts:set(username, {});
 	end
 	local salt = generate_uuid();
-	local valid, stored_key, server_key = getAuthenticationDatabaseSHA1(password, salt, iteration_count);
+	local valid, stored_key, server_key = getAuthenticationDatabaseSHA1(password, salt, default_iteration_count);
 	local stored_key_hex = to_hex(stored_key);
 	local server_key_hex = to_hex(server_key);
-	return accounts:set(username, {stored_key = stored_key_hex, server_key = server_key_hex, salt = salt, iteration_count = iteration_count});
+	return accounts:set(username, {stored_key = stored_key_hex, server_key = server_key_hex, salt = salt, iteration_count = default_iteration_count});
 end
 
 function provider.delete_user(username)
--- a/plugins/mod_c2s.lua	Wed May 21 13:19:57 2014 -0400
+++ b/plugins/mod_c2s.lua	Fri May 23 20:37:16 2014 +0100
@@ -174,19 +174,6 @@
 	end
 end
 
-local function session_open_stream(session)
-	local attr = {
-		["xmlns:stream"] = 'http://etherx.jabber.org/streams',
-		xmlns = stream_callbacks.default_ns,
-		version = "1.0",
-		["xml:lang"] = 'en',
-		id = session.streamid or "",
-		from = session.host
-	};
-	session.send("<?xml version='1.0'?>");
-	session.send(st.stanza("stream:stream", attr):top_tag());
-end
-
 module:hook_global("user-deleted", function(event)
 	local username, host = event.username, event.host;
 	local user = hosts[host].sessions[username];
@@ -234,7 +221,6 @@
 		conn:setoption("keepalive", opt_keepalives);
 	end
 
-	session.open_stream = session_open_stream;
 	session.close = session_close;
 
 	local stream = new_xmpp_stream(session, stream_callbacks);
--- a/plugins/mod_component.lua	Wed May 21 13:19:57 2014 -0400
+++ b/plugins/mod_component.lua	Fri May 23 20:37:16 2014 +0100
@@ -177,9 +177,7 @@
 	session.streamid = uuid_gen();
 	session.notopen = nil;
 	-- Return stream header
-	session.send("<?xml version='1.0'?>");
-	session.send(st.stanza("stream:stream", { xmlns=xmlns_component,
-			["xmlns:stream"]='http://etherx.jabber.org/streams', id=session.streamid, from=session.host }):top_tag());
+	session:open_stream();
 end
 
 function stream_callbacks.streamclosed(session)
--- a/plugins/mod_compression.lua	Wed May 21 13:19:57 2014 -0400
+++ b/plugins/mod_compression.lua	Fri May 23 20:37:16 2014 +0100
@@ -26,7 +26,7 @@
 
 module:hook("stream-features", function(event)
 	local origin, features = event.origin, event.features;
-	if not origin.compressed and (origin.type == "c2s" or origin.type == "s2sin" or origin.type == "s2sout") then
+	if not origin.compressed and origin.type == "c2s" then
 		-- FIXME only advertise compression support when TLS layer has no compression enabled
 		features:add_child(compression_stream_feature);
 	end
@@ -35,7 +35,7 @@
 module:hook("s2s-stream-features", function(event)
 	local origin, features = event.origin, event.features;
 	-- FIXME only advertise compression support when TLS layer has no compression enabled
-	if not origin.compressed and (origin.type == "c2s" or origin.type == "s2sin" or origin.type == "s2sout") then
+	if not origin.compressed and origin.type == "s2sin" then
 		features:add_child(compression_stream_feature);
 	end
 end);
@@ -43,13 +43,13 @@
 -- Hook to activate compression if remote server supports it.
 module:hook_stanza(xmlns_stream, "features",
 		function (session, stanza)
-			if not session.compressed and (session.type == "c2s" or session.type == "s2sin" or session.type == "s2sout") then
+			if not session.compressed and session.type == "s2sout" then
 				-- does remote server support compression?
-				local comp_st = stanza:child_with_name("compression");
+				local comp_st = stanza:get_child("compression", xmlns_compression_feature);
 				if comp_st then
 					-- do we support the mechanism
-					for a in comp_st:children() do
-						local algorithm = a[1]
+					for a in comp_st:childtags("method") do
+						local algorithm = a:get_text();
 						if algorithm == "zlib" then
 							session.sends2s(st.stanza("compress", {xmlns=xmlns_compression_protocol}):tag("method"):text("zlib"))
 							session.log("debug", "Enabled compression using zlib.")
@@ -125,8 +125,8 @@
 
 module:hook("stanza/http://jabber.org/protocol/compress:compressed", function(event)
 	local session = event.origin;
-
-	if session.type == "s2sout_unauthed" or session.type == "s2sout" then
+	
+	if session.type == "s2sout" then
 		session.log("debug", "Activating compression...")
 		-- create deflate and inflate streams
 		local deflate_stream = get_deflate_stream(session);
@@ -150,7 +150,7 @@
 module:hook("stanza/http://jabber.org/protocol/compress:compress", function(event)
 	local session, stanza = event.origin, event.stanza;
 
-	if session.type == "c2s" or session.type == "s2sin" or session.type == "c2s_unauthed" or session.type == "s2sin_unauthed" then
+	if session.type == "c2s" or session.type == "s2sin" then
 		-- fail if we are already compressed
 		if session.compressed then
 			local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("setup-failed");
@@ -160,8 +160,7 @@
 		end
 
 		-- checking if the compression method is supported
-		local method = stanza:child_with_name("method");
-		method = method and (method[1] or "");
+		local method = stanza:get_child_text("method");
 		if method == "zlib" then
 			session.log("debug", "zlib compression enabled.");
 
--- a/plugins/mod_http.lua	Wed May 21 13:19:57 2014 -0400
+++ b/plugins/mod_http.lua	Fri May 23 20:37:16 2014 +0100
@@ -42,7 +42,7 @@
 	return (normalize_path(host_module:get_option("http_paths", {})[app_name] -- Host
 		or module:get_option("http_paths", {})[app_name] -- Global
 		or default_app_path)) -- Default
-		:gsub("%$(%w+)", { host = module.host });
+		:gsub("%$(%w+)", { host = host_module.host });
 end
 
 local ports_by_scheme = { http = 80, https = 443, };
@@ -51,6 +51,9 @@
 function moduleapi.http_url(module, app_name, default_path)
 	app_name = app_name or (module.name:gsub("^http_", ""));
 	local external_url = url_parse(module:get_option_string("http_external_url")) or {};
+	if external_url.scheme and external_url.port == nil then
+		external_url.port = ports_by_scheme[external_url.scheme];
+	end
 	local services = portmanager.get_active_services();
 	local http_services = services:get("https") or services:get("http") or {};
 	for interface, ports in pairs(http_services) do
@@ -139,7 +142,13 @@
 	listener = server.listener;
 	default_port = 5281;
 	encryption = "ssl";
-	ssl_config = { verify = "none" };
+	ssl_config = {
+		verify = {
+			peer = false,
+			client_once = false,
+			"none",
+		}
+	};
 	multiplex = {
 		pattern = "^[A-Z]";
 	};
--- a/plugins/mod_http_files.lua	Wed May 21 13:19:57 2014 -0400
+++ b/plugins/mod_http_files.lua	Fri May 23 20:37:16 2014 +0100
@@ -14,6 +14,7 @@
 local open = io.open;
 local stat = lfs.attributes;
 local build_path = require"socket.url".build_path;
+local path_sep = package.config:sub(1,1);
 
 local base_path = module:get_option_string("http_files_dir", module:get_option_string("http_path"));
 local dir_indices = module:get_option("http_index_files", { "index.html", "index.htm" });
@@ -61,7 +62,7 @@
 		local request, response = event.request, event.response;
 		local orig_path = request.path;
 		local full_path = base_path .. (path and "/"..path or "");
-		local attr = stat(full_path);
+		local attr = stat((full_path:gsub('%'..path_sep..'+$','')));
 		if not attr then
 			return 404;
 		end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/mod_pep_plus.lua	Fri May 23 20:37:16 2014 +0100
@@ -0,0 +1,368 @@
+local pubsub = require "util.pubsub";
+local jid_bare = require "util.jid".bare;
+local jid_split = require "util.jid".split;
+local set_new = require "util.set".new;
+local st = require "util.stanza";
+local calculate_hash = require "util.caps".calculate_hash;
+local is_contact_subscribed = require "core.rostermanager".is_contact_subscribed;
+
+local xmlns_pubsub = "http://jabber.org/protocol/pubsub";
+local xmlns_pubsub_event = "http://jabber.org/protocol/pubsub#event";
+local xmlns_pubsub_owner = "http://jabber.org/protocol/pubsub#owner";
+
+local lib_pubsub = module:require "pubsub";
+local handlers = lib_pubsub.handlers;
+local pubsub_error_reply = lib_pubsub.pubsub_error_reply;
+
+local services = {};
+local recipients = {};
+local hash_map = {};
+
+function module.save()
+	return { services = services };
+end
+
+function module.restore(data)
+	services = data.services;
+end
+
+local function subscription_presence(user_bare, recipient)
+	local recipient_bare = jid_bare(recipient);
+	if (recipient_bare == user_bare) then return true; end
+	local username, host = jid_split(user_bare);
+	return is_contact_subscribed(username, host, recipient_bare);
+end
+
+local function get_broadcaster(name)
+	local function simple_broadcast(kind, node, jids, item)
+		if item then
+			item = st.clone(item);
+			item.attr.xmlns = nil; -- Clear the pubsub namespace
+		end
+		local message = st.message({ from = name, type = "headline" })
+			:tag("event", { xmlns = xmlns_pubsub_event })
+				:tag(kind, { node = node })
+					:add_child(item);
+		for jid in pairs(jids) do
+			module:log("debug", "Sending notification to %s from %s: %s", jid, name, tostring(item));
+			message.attr.to = jid;
+			module:send(message);
+		end
+	end
+	return simple_broadcast;
+end
+
+local function get_pep_service(name)
+	if services[name] then
+		return services[name];
+	end
+	services[name] = pubsub.new({
+		capabilities = {
+			none = {
+				create = false;
+				publish = false;
+				retract = false;
+				get_nodes = false;
+
+				subscribe = false;
+				unsubscribe = false;
+				get_subscription = false;
+				get_subscriptions = false;
+				get_items = false;
+
+				subscribe_other = false;
+				unsubscribe_other = false;
+				get_subscription_other = false;
+				get_subscriptions_other = false;
+
+				be_subscribed = true;
+				be_unsubscribed = true;
+
+				set_affiliation = false;
+			};
+			subscriber = {
+				create = false;
+				publish = false;
+				retract = false;
+				get_nodes = true;
+
+				subscribe = true;
+				unsubscribe = true;
+				get_subscription = true;
+				get_subscriptions = true;
+				get_items = true;
+
+				subscribe_other = false;
+				unsubscribe_other = false;
+				get_subscription_other = false;
+				get_subscriptions_other = false;
+
+				be_subscribed = true;
+				be_unsubscribed = true;
+
+				set_affiliation = false;
+			};
+			publisher = {
+				create = false;
+				publish = true;
+				retract = true;
+				get_nodes = true;
+
+				subscribe = true;
+				unsubscribe = true;
+				get_subscription = true;
+				get_subscriptions = true;
+				get_items = true;
+
+				subscribe_other = false;
+				unsubscribe_other = false;
+				get_subscription_other = false;
+				get_subscriptions_other = false;
+
+				be_subscribed = true;
+				be_unsubscribed = true;
+
+				set_affiliation = false;
+			};
+			owner = {
+				create = true;
+				publish = true;
+				retract = true;
+				delete = true;
+				get_nodes = true;
+
+				subscribe = true;
+				unsubscribe = true;
+				get_subscription = true;
+				get_subscriptions = true;
+				get_items = true;
+
+
+				subscribe_other = true;
+				unsubscribe_other = true;
+				get_subscription_other = true;
+				get_subscriptions_other = true;
+
+				be_subscribed = true;
+				be_unsubscribed = true;
+
+				set_affiliation = true;
+			};
+		};
+
+		autocreate_on_publish = true;
+		autocreate_on_subscribe = true;
+
+		broadcaster = get_broadcaster(name);
+		get_affiliation = function (jid)
+			if jid_bare(jid) == name then
+				return "owner";
+			elseif subscription_presence(name, jid) then
+				return "subscriber";
+			end
+		end;
+
+		normalize_jid = jid_bare;
+	});
+	return services[name];
+end
+
+function handle_pubsub_iq(event)
+	local origin, stanza = event.origin, event.stanza;
+	local pubsub = stanza.tags[1];
+	local action = pubsub.tags[1];
+	if not action then
+		return origin.send(st.error_reply(stanza, "cancel", "bad-request"));
+	end
+	local service_name = stanza.attr.to or origin.username.."@"..origin.host
+	local service = get_pep_service(service_name);
+	local handler = handlers[stanza.attr.type.."_"..action.name];
+	if handler then
+		handler(origin, stanza, action, service);
+		return true;
+	end
+end
+
+module:hook("iq/bare/"..xmlns_pubsub..":pubsub", handle_pubsub_iq);
+module:hook("iq/bare/"..xmlns_pubsub_owner..":pubsub", handle_pubsub_iq);
+
+module:add_identity("pubsub", "pep", module:get_option_string("name", "Prosody"));
+module:add_feature("http://jabber.org/protocol/pubsub#publish");
+
+local function get_caps_hash_from_presence(stanza, current)
+	local t = stanza.attr.type;
+	if not t then
+		local child = stanza:get_child("c", "http://jabber.org/protocol/caps");
+		if child then
+			local attr = child.attr;
+			if attr.hash then -- new caps
+				if attr.hash == 'sha-1' and attr.node and attr.ver then
+					return attr.ver, attr.node.."#"..attr.ver;
+				end
+			else -- legacy caps
+				if attr.node and attr.ver then
+					return attr.node.."#"..attr.ver.."#"..(attr.ext or ""), attr.node.."#"..attr.ver;
+				end
+			end
+		end
+		return; -- no or bad caps
+	elseif t == "unavailable" or t == "error" then
+		return;
+	end
+	return current; -- no caps, could mean caps optimization, so return current
+end
+
+local function resend_last_item(jid, node, service)
+	local ok, items = service:get_items(node, jid);
+	if not ok then return; end
+	for i, id in ipairs(items) do
+		service.config.broadcaster("items", node, { [jid] = true }, items[id]);
+	end
+end
+
+local function update_subscriptions(recipient, service_name, nodes)
+	local service = get_pep_service(service_name);
+
+	recipients[service_name] = recipients[service_name] or {};
+	nodes = nodes or set_new();
+	local old = recipients[service_name][recipient];
+
+	if old and type(old) == table then
+		for node in pairs((old - nodes):items()) do
+			service:remove_subscription(node, recipient, recipient);
+		end
+	end
+
+	for node in nodes:items() do
+		service:add_subscription(node, recipient, recipient);
+		resend_last_item(recipient, node, service);
+	end
+	recipients[service_name][recipient] = nodes;
+end
+
+module:hook("presence/bare", function(event)
+	-- inbound presence to bare JID recieved
+	local origin, stanza = event.origin, event.stanza;
+	local user = stanza.attr.to or (origin.username..'@'..origin.host);
+	local t = stanza.attr.type;
+	local self = not stanza.attr.to;
+	local service = get_pep_service(user);
+
+	if not t then -- available presence
+		if self or subscription_presence(user, stanza.attr.from) then
+			local recipient = stanza.attr.from;
+			local current = recipients[user] and recipients[user][recipient];
+			local hash, query_node = get_caps_hash_from_presence(stanza, current);
+			if current == hash or (current and current == hash_map[hash]) then return; end
+			if not hash then
+				update_subscriptions(recipient, user);
+			else
+				recipients[user] = recipients[user] or {};
+				if hash_map[hash] then
+					update_subscriptions(recipient, user, hash_map[hash]);
+				else
+					recipients[user][recipient] = hash;
+					local from_bare = origin.type == "c2s" and origin.username.."@"..origin.host;
+					if self or origin.type ~= "c2s" or (recipients[from_bare] and recipients[from_bare][origin.full_jid]) ~= hash then
+						-- COMPAT from ~= stanza.attr.to because OneTeam can't deal with missing from attribute
+						origin.send(
+							st.stanza("iq", {from=user, to=stanza.attr.from, id="disco", type="get"})
+								:tag("query", {xmlns = "http://jabber.org/protocol/disco#info", node = query_node})
+						);
+					end
+				end
+			end
+		end
+	elseif t == "unavailable" then
+		update_subscriptions(stanza.attr.from, user);
+	elseif not self and t == "unsubscribe" then
+		local from = jid_bare(stanza.attr.from);
+		local subscriptions = recipients[user];
+		if subscriptions then
+			for subscriber in pairs(subscriptions) do
+				if jid_bare(subscriber) == from then
+					update_subscriptions(subscriber, user);
+				end
+			end
+		end
+	end
+end, 10);
+
+module:hook("iq-result/bare/disco", function(event)
+	local origin, stanza = event.origin, event.stanza;
+	local disco = stanza:get_child("query", "http://jabber.org/protocol/disco#info");
+	if not disco then
+		return;
+	end
+
+	-- Process disco response
+	local self = not stanza.attr.to;
+	local user = stanza.attr.to or (origin.username..'@'..origin.host);
+	local contact = stanza.attr.from;
+	local current = recipients[user] and recipients[user][contact];
+	if type(current) ~= "string" then return; end -- check if waiting for recipient's response
+	local ver = current;
+	if not string.find(current, "#") then
+		ver = calculate_hash(disco.tags); -- calculate hash
+	end
+	local notify = set_new();
+	for _, feature in pairs(disco.tags) do
+		if feature.name == "feature" and feature.attr.var then
+			local nfeature = feature.attr.var:match("^(.*)%+notify$");
+			if nfeature then notify:add(nfeature); end
+		end
+	end
+	hash_map[ver] = notify; -- update hash map
+	if self then
+		for jid, item in pairs(origin.roster) do -- for all interested contacts
+			if item.subscription == "both" or item.subscription == "from" then
+				if not recipients[jid] then recipients[jid] = {}; end
+				update_subscriptions(contact, jid, notify);
+			end
+		end
+	end
+	update_subscriptions(contact, user, notify);
+end);
+
+module:hook("account-disco-info-node", function(event)
+	local reply, stanza, origin = event.reply, event.stanza, event.origin;
+	local service_name = stanza.attr.to or origin.username.."@"..origin.host
+	local service = get_pep_service(service_name);
+	local node = event.node;
+	local ok = service:get_items(node, jid_bare(stanza.attr.from) or true);
+	if not ok then return; end
+	event.exists = true;
+	reply:tag('identity', {category='pubsub', type='leaf'}):up();
+end);
+
+module:hook("account-disco-info", function(event)
+	local reply = event.reply;
+	reply:tag('identity', {category='pubsub', type='pep'}):up();
+	reply:tag('feature', {var='http://jabber.org/protocol/pubsub#publish'}):up();
+end);
+
+module:hook("account-disco-items-node", function(event)
+	local reply, stanza, origin = event.reply, event.stanza, event.origin;
+	local node = event.node;
+	local service_name = stanza.attr.to or origin.username.."@"..origin.host
+	local service = get_pep_service(service_name);
+	local ok, ret = service:get_items(node, jid_bare(stanza.attr.from) or true);
+	if not ok then return; end
+	event.exists = true;
+	for _, id in ipairs(ret) do
+		reply:tag("item", { jid = service_name, name = id }):up();
+	end
+end);
+
+module:hook("account-disco-items", function(event)
+	local reply, stanza, origin = event.reply, event.stanza, event.origin;
+
+	local service_name = reply.attr.from or origin.username.."@"..origin.host
+	local service = get_pep_service(service_name);
+	local ok, ret = service:get_nodes(jid_bare(stanza.attr.from));
+	if not ok then return; end
+
+	for node, node_obj in pairs(ret) do
+		reply:tag("item", { jid = service_name, node = node, name = node_obj.config.name }):up();
+	end
+end);
--- a/plugins/mod_posix.lua	Wed May 21 13:19:57 2014 -0400
+++ b/plugins/mod_posix.lua	Fri May 23 20:37:16 2014 +0100
@@ -129,14 +129,6 @@
 require "core.loggingmanager".register_sink_type("syslog", syslog_sink_maker);
 
 local daemonize = module:get_option("daemonize", prosody.installed);
-if daemonize == nil then
-	local no_daemonize = module:get_option("no_daemonize"); --COMPAT w/ 0.5
-	daemonize = not no_daemonize;
-	if no_daemonize ~= nil then
-		module:log("warn", "The 'no_daemonize' option is now replaced by 'daemonize'");
-		module:log("warn", "Update your config from 'no_daemonize = %s' to 'daemonize = %s'", tostring(no_daemonize), tostring(daemonize));
-	end
-end
 
 local function remove_log_sinks()
 	local lm = require "core.loggingmanager";
--- a/plugins/mod_proxy65.lua	Wed May 21 13:19:57 2014 -0400
+++ b/plugins/mod_proxy65.lua	Fri May 23 20:37:16 2014 +0100
@@ -101,27 +101,10 @@
 		module:log("warn", "proxy65_port is deprecated, please put proxy65_ports = { %d } into the global section instead", legacy_config);
 	end
 
+	module:depends("disco");
 	module:add_identity("proxy", "bytestreams", name);
 	module:add_feature("http://jabber.org/protocol/bytestreams");
 
-	module:hook("iq-get/host/http://jabber.org/protocol/disco#info:query", function(event)
-		local origin, stanza = event.origin, event.stanza;
-		if not stanza.tags[1].attr.node then
-			origin.send(st.reply(stanza):query("http://jabber.org/protocol/disco#info")
-				:tag("identity", {category='proxy', type='bytestreams', name=name}):up()
-				:tag("feature", {var="http://jabber.org/protocol/bytestreams"}) );
-			return true;
-		end
-	end, -1);
-
-	module:hook("iq-get/host/http://jabber.org/protocol/disco#items:query", function(event)
-		local origin, stanza = event.origin, event.stanza;
-		if not stanza.tags[1].attr.node then
-			origin.send(st.reply(stanza):query("http://jabber.org/protocol/disco#items"));
-			return true;
-		end
-	end, -1);
-
 	module:hook("iq-get/host/http://jabber.org/protocol/bytestreams:query", function(event)
 		local origin, stanza = event.origin, event.stanza;
 
--- a/plugins/mod_pubsub/mod_pubsub.lua	Wed May 21 13:19:57 2014 -0400
+++ b/plugins/mod_pubsub/mod_pubsub.lua	Fri May 23 20:37:16 2014 +0100
@@ -100,7 +100,7 @@
 		return;
 	end
 
-	for id, item in pairs(ret) do
+	for _, id in ipairs(ret) do
 		reply:tag("item", { jid = module.host, name = id }):up();
 	end
 	event.exists = true;
--- a/plugins/mod_pubsub/pubsub.lib.lua	Wed May 21 13:19:57 2014 -0400
+++ b/plugins/mod_pubsub/pubsub.lib.lua	Fri May 23 20:37:16 2014 +0100
@@ -42,8 +42,8 @@
 	end
 
 	local data = st.stanza("items", { node = node });
-	for _, entry in pairs(results) do
-		data:add_child(entry);
+	for _, id in ipairs(results) do
+		data:add_child(results[id]);
 	end
 	local reply;
 	if data then
--- a/plugins/mod_s2s/mod_s2s.lua	Wed May 21 13:19:57 2014 -0400
+++ b/plugins/mod_s2s/mod_s2s.lua	Fri May 23 20:37:16 2014 +0100
@@ -150,6 +150,13 @@
 	module:hook("route/remote", route_to_new_session, -10);
 	module:hook("s2s-authenticated", make_authenticated, -1);
 	module:hook("s2s-read-timeout", keepalive, -1);
+	module:hook_stanza("http://etherx.jabber.org/streams", "features", function (session, stanza)
+		if session.type == "s2sout" then
+			-- Stream is authenticated and we are seem to be done with feature negotiation,
+			-- so the stream is ready for stanzas.  RFC 6120 Section 4.3
+			mark_connected(session);
+		end
+	end, -1);
 end
 
 -- Stream is authorised, and ready for normal stanzas
@@ -219,7 +226,10 @@
 	end
 	session.log("debug", "connection %s->%s is now authenticated for %s", session.from_host, session.to_host, host);
 
-	mark_connected(session);
+	if (session.type == "s2sout" and session.external_auth ~= "succeeded") or session.type == "s2sin" then
+		-- Stream either used dialback for authentication or is an incoming stream.
+		mark_connected(session);
+	end
 
 	return true;
 end
@@ -510,22 +520,10 @@
 	end
 end
 
-function session_open_stream(session, from, to)
-	local attr = {
-		["xmlns:stream"] = 'http://etherx.jabber.org/streams',
-		xmlns = 'jabber:server',
-		version = session.version and (session.version > 0 and "1.0" or nil),
-		["xml:lang"] = 'en',
-		id = session.streamid,
-		from = from, to = to,
-	}
+function session_stream_attrs(session, from, to, attr)
 	if not from or (hosts[from] and hosts[from].modules.dialback) then
 		attr["xmlns:db"] = 'jabber:server:dialback';
 	end
-
-	session.sends2s("<?xml version='1.0'?>");
-	session.sends2s(st.stanza("stream:stream", attr):top_tag());
-	return true;
 end
 
 -- Session initialization logic shared by incoming and outgoing
@@ -540,7 +538,7 @@
 		session.stream:reset();
 	end
 
-	session.open_stream = session_open_stream;
+	session.stream_attrs = session_stream_attrs;
 
 	local filter = session.filter;
 	function session.data(data)
--- a/plugins/mod_saslauth.lua	Wed May 21 13:19:57 2014 -0400
+++ b/plugins/mod_saslauth.lua	Fri May 23 20:37:16 2014 +0100
@@ -197,7 +197,7 @@
 		return s2s_external_auth(session, stanza)
 	end
 
-	if session.type ~= "c2s_unauthed" then return; end
+	if session.type ~= "c2s_unauthed" or module:get_host_type() ~= "local" 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
--- a/plugins/mod_storage_sql.lua	Wed May 21 13:19:57 2014 -0400
+++ b/plugins/mod_storage_sql.lua	Fri May 23 20:37:16 2014 +0100
@@ -49,7 +49,7 @@
 end
 
 
-local resolve_relative_path = require "core.configmanager".resolve_relative_path;
+local resolve_relative_path = require "util.paths".resolve_relative_path;
 
 local function test_connection()
 	if not connection then return nil; end
--- a/plugins/mod_storage_sql2.lua	Wed May 21 13:19:57 2014 -0400
+++ b/plugins/mod_storage_sql2.lua	Fri May 23 20:37:16 2014 +0100
@@ -2,7 +2,7 @@
 local json = require "util.json";
 local xml_parse = require "util.xml".parse;
 local uuid = require "util.uuid";
-local resolve_relative_path = require "core.configmanager".resolve_relative_path;
+local resolve_relative_path = require "util.paths".resolve_relative_path;
 
 local stanza_mt = require"util.stanza".stanza_mt;
 local getmetatable = getmetatable;
@@ -289,7 +289,7 @@
 
 		-- Total matching
 		if query.total then
-			local stats = engine:select(sql_query:gsub("^(SELECT).-(FROM)", "%1 COUNT(*) %2"):format(t_concat(where, " AND "), "DESC", ""), unpack(args));
+			local stats = engine:select("SELECT COUNT(*) FROM `prosodyarchive` WHERE " .. t_concat(where, " AND "), unpack(args));
 			if stats then
 				local _total = stats()
 				total = _total and _total[1];
--- a/prosody	Wed May 21 13:19:57 2014 -0400
+++ b/prosody	Fri May 23 20:37:16 2014 +0100
@@ -49,9 +49,6 @@
 
 -- Check dependencies
 local dependencies = require "util.dependencies";
-if not dependencies.check_dependencies() then
-	os.exit(1);
-end
 
 -- Load the config-parsing module
 config = require "core.configmanager"
@@ -116,6 +113,12 @@
 	end
 end
 
+function check_dependencies()
+	if not dependencies.check_dependencies() then
+		os.exit(1);
+	end
+end
+
 function load_libraries()
 	-- Load socket framework
 	server = require "net.server"
@@ -382,6 +385,7 @@
 sanity_check();
 sandbox_require();
 set_function_metatable();
+check_dependencies();
 load_libraries();
 init_global_state();
 read_version();
--- a/prosody.cfg.lua.dist	Wed May 21 13:19:57 2014 -0400
+++ b/prosody.cfg.lua.dist	Fri May 23 20:37:16 2014 +0100
@@ -63,7 +63,6 @@
 		--"http_files"; -- Serve static files from a directory over HTTP
 
 	-- Other specific functionality
-		--"posix"; -- POSIX functionality, sends server to background, enables syslog, etc.
 		--"groups"; -- Shared roster support
 		--"announce"; -- Send announcement to all online users
 		--"welcome"; -- Welcome users who register accounts
@@ -78,6 +77,7 @@
 	-- "offline"; -- Store offline messages
 	-- "c2s"; -- Handle client connections
 	-- "s2s"; -- Handle server-to-server connections
+	-- "posix"; -- POSIX functionality, sends server to background, enables syslog, etc.
 }
 
 -- Disable account creation by default, for security
--- a/prosodyctl	Wed May 21 13:19:57 2014 -0400
+++ b/prosodyctl	Fri May 23 20:37:16 2014 +0100
@@ -414,7 +414,11 @@
 	
 	local ok, ret = prosodyctl.start();
 	if ok then
-		if config.get("*", "daemonize") ~= false then
+		local daemonize = config.get("*", "daemonize");
+		if daemonize == nil then
+			daemonize = prosody.installed;
+		end
+		if daemonize then
 			local i=1;
 			while true do
 				local ok, running = prosodyctl.isrunning();
@@ -687,7 +691,12 @@
 				conf.distinguished_name[k] = nv ~= "." and nv or nil;
 			end
 		end
-		local conf_file = io.open(conf_filename, "w");
+		local conf_file, err = io.open(conf_filename, "w");
+		if not conf_file then
+			show_warning("Could not open OpenSSL config file for writing");
+			show_warning(err);
+			os.exit(1);
+		end
 		conf_file:write(conf:serialize());
 		conf_file:close();
 		print("");
@@ -788,8 +797,28 @@
 	local array, set = require "util.array", require "util.set";
 	local it = require "util.iterators";
 	local ok = true;
+	local function disabled_hosts(host, conf) return host ~= "*" and conf.enabled ~= false; end
+	local function enabled_hosts() return it.filter(disabled_hosts, pairs(config.getconfig())); end
+	if not what or what == "disabled" then
+		local disabled_hosts = set.new();
+		for host, host_options in it.filter("*", pairs(config.getconfig())) do
+			if host_options.enabled == false then
+				disabled_hosts:add(host);
+			end
+		end
+		if not disabled_hosts:empty() then
+			local msg = "Checks will be skipped for these disabled hosts: %s";
+			if what then msg = "These hosts are disabled: %s"; end
+			show_warning(msg, tostring(disabled_hosts));
+			if what then return 0; end
+			print""
+		end
+	end
 	if not what or what == "config" then
 		print("Checking config...");
+		local deprecated = set.new({
+			"bosh_ports", "disallow_s2s", "no_daemonize", "anonymous_login",
+		});
 		local known_global_options = set.new({
 			"pidfile", "log", "plugin_paths", "prosody_user", "prosody_group", "daemonize",
 			"umask", "prosodyctl_timeout", "use_ipv6", "use_libevent", "network_settings"
@@ -802,9 +831,27 @@
 			print("    No global options defined. Perhaps you have put a host definition at the top")
 			print("    of the config file? They should be at the bottom, see http://prosody.im/doc/configure#overview");
 		end
+		if it.count(enabled_hosts()) == 0 then
+			ok = false;
+			print("");
+			if it.count(it.filter("*", pairs(config))) == 0 then
+				print("    No hosts are defined, please add at least one VirtualHost section")
+			elseif config["*"]["enabled"] == false then
+				print("    No hosts are enabled. Remove enabled = false from the global section or put enabled = true under at least one VirtualHost section")
+			else
+				print("    All hosts are disabled. Remove enabled = false from at least one VirtualHost section")
+			end
+		end
 		-- Check for global options under hosts
 		local global_options = set.new(it.to_array(it.keys(config["*"])));
-		for host, options in it.filter("*", pairs(config)) do
+		local deprecated_global_options = set.intersection(global_options, deprecated);
+		if not deprecated_global_options:empty() then
+			print("");
+			print("    You have some deprecated options in the global section:");
+			print("    "..tostring(deprecated_global_options))
+			ok = false;
+		end
+		for host, options in enabled_hosts() do
 			local host_options = set.new(it.to_array(it.keys(options)));
 			local misplaced_options = set.intersection(host_options, known_global_options);
 			for name in pairs(options) do
@@ -889,7 +936,7 @@
 		
 		local v6_supported = not not socket.tcp6;
 		
-		for host, host_options in it.filter("*", pairs(config.getconfig())) do
+		for host, host_options in enabled_hosts() do
 			local all_targets_ok, some_targets_ok = true, false;
 			
 			local is_component = not not host_options.component_module;
@@ -1038,54 +1085,52 @@
 			print("This version of LuaSec (" .. ssl._VERSION .. ") does not support certificate checking");
 			cert_ok = false
 		else
-			for host in pairs(hosts) do
-				if host ~= "*" then -- Should check global certs too.
-					print("Checking certificate for "..host);
-					-- First, let's find out what certificate this host uses.
-					local ssl_config = config.rawget(host, "ssl");
-					if not ssl_config then
-						local base_host = host:match("%.(.*)");
-						ssl_config = config.get(base_host, "ssl");
-					end
-					if not ssl_config then
-						print("  No 'ssl' option defined for "..host)
-						cert_ok = false
-					elseif not ssl_config.certificate then
-						print("  No 'certificate' set in ssl option for "..host)
-						cert_ok = false
-					elseif not ssl_config.key then
-						print("  No 'key' set in ssl option for "..host)
+			for host in enabled_hosts() do
+				print("Checking certificate for "..host);
+				-- First, let's find out what certificate this host uses.
+				local ssl_config = config.rawget(host, "ssl");
+				if not ssl_config then
+					local base_host = host:match("%.(.*)");
+					ssl_config = config.get(base_host, "ssl");
+				end
+				if not ssl_config then
+					print("  No 'ssl' option defined for "..host)
+					cert_ok = false
+				elseif not ssl_config.certificate then
+					print("  No 'certificate' set in ssl option for "..host)
+					cert_ok = false
+				elseif not ssl_config.key then
+					print("  No 'key' set in ssl option for "..host)
+					cert_ok = false
+				else
+					local key, err = io.open(ssl_config.key); -- Permissions check only
+					if not key then
+						print("    Could not open "..ssl_config.key..": "..err);
 						cert_ok = false
 					else
-						local key, err = io.open(ssl_config.key); -- Permissions check only
-						if not key then
-							print("    Could not open "..ssl_config.key..": "..err);
-							cert_ok = false
-						else
-							key:close();
-						end
-						local cert_fh, err = io.open(ssl_config.certificate); -- Load the file.
-						if not cert_fh then
-							print("    Could not open "..ssl_config.certificate..": "..err);
+						key:close();
+					end
+					local cert_fh, err = io.open(ssl_config.certificate); -- Load the file.
+					if not cert_fh then
+						print("    Could not open "..ssl_config.certificate..": "..err);
+						cert_ok = false
+					else
+						print("  Certificate: "..ssl_config.certificate)
+						local cert = load_cert(cert_fh:read"*a"); cert_fh = cert_fh:close();
+						if not cert:validat(os.time()) then
+							print("    Certificate has expired.")
 							cert_ok = false
-						else
-							print("  Certificate: "..ssl_config.certificate)
-							local cert = load_cert(cert_fh:read"*a"); cert_fh = cert_fh:close();
-							if not cert:validat(os.time()) then
-								print("    Certificate has expired.")
-								cert_ok = false
-							end
-							if config.get(host, "component_module") == nil
+						end
+						if config.get(host, "component_module") == nil
 							and not x509_verify_identity(host, "_xmpp-client", cert) then
-								print("    Not vaild for client connections to "..host..".")
-								cert_ok = false
-							end
-							if (not (config.get(name, "anonymous_login")
-								or config.get(name, "authentication") == "anonymous"))
+							print("    Not vaild for client connections to "..host..".")
+							cert_ok = false
+						end
+						if (not (config.get(host, "anonymous_login")
+							or config.get(host, "authentication") == "anonymous"))
 							and not x509_verify_identity(host, "_xmpp-client", cert) then
-								print("    Not vaild for server-to-server connections to "..host..".")
-								cert_ok = false
-							end
+							print("    Not vaild for server-to-server connections to "..host..".")
+							cert_ok = false
 						end
 					end
 				end
--- a/tools/ejabberd2prosody.lua	Wed May 21 13:19:57 2014 -0400
+++ b/tools/ejabberd2prosody.lua	Fri May 23 20:37:16 2014 +0100
@@ -44,8 +44,10 @@
 		for _, a in ipairs(tuple[4]) do build_stanza(a, stanza); end
 		if up then stanza:up(); else return stanza end
 	elseif tuple[1] == "xmlcdata" then
-		assert(type(tuple[2]) == "string", "XML CDATA has unexpected type: "..type(tuple[2]));
-		stanza:text(tuple[2]);
+		if type(tuple[2]) ~= "table" then
+			assert(type(tuple[2]) == "string", "XML CDATA has unexpected type: "..type(tuple[2]));
+			stanza:text(tuple[2]);
+		end -- else it's [], i.e., the null value, used for the empty string
 	else
 		error("unknown element type: "..serialize(tuple));
 	end
--- a/tools/jabberd14sql2prosody.lua	Wed May 21 13:19:57 2014 -0400
+++ b/tools/jabberd14sql2prosody.lua	Fri May 23 20:37:16 2014 +0100
@@ -428,7 +428,7 @@
 end
 
 -- import modules
-package.path = package.path.."..\?.lua;";
+package.path = package.path..";../?.lua;";
 
 local my_name = arg[0];
 if my_name:match("[/\\]") then
--- a/util-src/pposix.c	Wed May 21 13:19:57 2014 -0400
+++ b/util-src/pposix.c	Fri May 23 20:37:16 2014 +0100
@@ -674,6 +674,7 @@
 #if _XOPEN_SOURCE >= 600 || _POSIX_C_SOURCE >= 200112L || defined(_GNU_SOURCE)
 int lc_fallocate(lua_State* L)
 {
+	int ret;
 	off_t offset, len;
 	FILE *f = *(FILE**) luaL_checkudata(L, 1, LUA_FILEHANDLE);
 	if (f == NULL)
@@ -683,11 +684,15 @@
 	len = luaL_checkinteger(L, 3);
 
 #if defined(__linux__) && defined(_GNU_SOURCE)
-	if(fallocate(fileno(f), FALLOC_FL_KEEP_SIZE, offset, len) == 0)
+	errno = 0;
+	ret = fallocate(fileno(f), FALLOC_FL_KEEP_SIZE, offset, len);
+	if(ret == 0)
 	{
 		lua_pushboolean(L, 1);
 		return 1;
 	}
+	/* Some old versions of Linux apparently use the return value instead of errno */
+	if(errno == 0) errno = ret;
 
 	if(errno != ENOSYS && errno != EOPNOTSUPP)
 	{
@@ -701,7 +706,8 @@
 #warning Note that posix_fallocate() will still be used on filesystems that dont support fallocate()
 #endif
 
-	if(posix_fallocate(fileno(f), offset, len) == 0)
+	ret = posix_fallocate(fileno(f), offset, len);
+	if(ret == 0)
 	{
 		lua_pushboolean(L, 1);
 		return 1;
@@ -709,7 +715,7 @@
 	else
 	{
 		lua_pushnil(L);
-		lua_pushstring(L, strerror(errno));
+		lua_pushstring(L, strerror(ret));
 		/* posix_fallocate() can leave a bunch of NULs at the end, so we cut that
 		 * this assumes that offset == length of the file */
 		ftruncate(fileno(f), offset);
--- a/util/dataforms.lua	Wed May 21 13:19:57 2014 -0400
+++ b/util/dataforms.lua	Fri May 23 20:37:16 2014 +0100
@@ -94,6 +94,15 @@
 			end
 		end
 
+		local media = field.media;
+		if media then
+			form:tag("media", { xmlns = "urn:xmpp:media-element", height = media.height, width = media.width });
+			for _, val in ipairs(media) do
+				form:tag("uri", { type = val.type }):text(val.uri):up()
+			end
+			form:up();
+		end
+
 		if field.required then
 			form:tag("required"):up();
 		end
--- a/util/dependencies.lua	Wed May 21 13:19:57 2014 -0400
+++ b/util/dependencies.lua	Fri May 23 20:37:16 2014 +0100
@@ -49,6 +49,14 @@
 end;
 
 function check_dependencies()
+	if _VERSION ~= "Lua 5.1" then
+		print "***********************************"
+		print("Unsupported Lua version: ".._VERSION);
+		print("Only Lua 5.1 is supported.");
+		print "***********************************"
+		return false;
+	end
+
 	local fatal;
 
 	local lxp = softreq "lxp"
@@ -140,7 +148,15 @@
 		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 "
+				.."LuaExpat 1.3.0 or higher as soon as possible. See "
+				.."http://prosody.im/doc/depends#luaexpat for more information.");
+		end
+		if not lxp.new({}).getcurrentbytecount then
+			log("error", "The version of LuaExpat on your system does not support "
+				.."stanza size limits, which may leave servers on untrusted "
+				.."networks (e.g. the internet) vulnerable to denial-of-service "
+				.."attacks. You should upgrade to LuaExpat 1.3.0 or higher as "
+				.."soon as possible. See "
 				.."http://prosody.im/doc/depends#luaexpat for more information.");
 		end
 	end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/indexedbheap.lua	Fri May 23 20:37:16 2014 +0100
@@ -0,0 +1,157 @@
+
+local setmetatable = setmetatable;
+local math_floor = math.floor;
+local t_remove = table.remove;
+
+local function _heap_insert(self, item, sync, item2, index)
+	local pos = #self + 1;
+	while true do
+		local half_pos = math_floor(pos / 2);
+		if half_pos == 0 or item > self[half_pos] then break; end
+		self[pos] = self[half_pos];
+		sync[pos] = sync[half_pos];
+		index[sync[pos]] = pos;
+		pos = half_pos;
+	end
+	self[pos] = item;
+	sync[pos] = item2;
+	index[item2] = pos;
+end
+
+local function _percolate_up(self, k, sync, index)
+	local tmp = self[k];
+	local tmp_sync = sync[k];
+	while k ~= 1 do
+		local parent = math_floor(k/2);
+		if tmp < self[parent] then break; end
+		self[k] = self[parent];
+		sync[k] = sync[parent];
+		index[sync[k]] = k;
+		k = parent;
+	end
+	self[k] = tmp;
+	sync[k] = tmp_sync;
+	index[tmp_sync] = k;
+	return k;
+end
+
+local function _percolate_down(self, k, sync, index)
+	local tmp = self[k];
+	local tmp_sync = sync[k];
+	local size = #self;
+	local child = 2*k;
+	while 2*k <= size do
+		if child ~= size and self[child] > self[child + 1] then
+			child = child + 1;
+		end
+		if tmp > self[child] then
+			self[k] = self[child];
+			sync[k] = sync[child];
+			index[sync[k]] = k;
+		else
+			break;
+		end
+
+		k = child;
+		child = 2*k;
+	end
+	self[k] = tmp;
+	sync[k] = tmp_sync;
+	index[tmp_sync] = k;
+	return k;
+end
+
+local function _heap_pop(self, sync, index)
+	local size = #self;
+	if size == 0 then return nil; end
+
+	local result = self[1];
+	local result_sync = sync[1];
+	index[result_sync] = nil;
+	if size == 1 then
+		self[1] = nil;
+		sync[1] = nil;
+		return result, result_sync;
+	end
+	self[1] = t_remove(self);
+	sync[1] = t_remove(sync);
+	index[sync[1]] = 1;
+
+	_percolate_down(self, 1, sync, index);
+
+	return result, result_sync;
+end
+
+local indexed_heap = {};
+
+function indexed_heap:insert(item, priority, id)
+	if id == nil then
+		id = self.current_id;
+		self.current_id = id + 1;
+	end
+	self.items[id] = item;
+	_heap_insert(self.priorities, priority, self.ids, id, self.index);
+	return id;
+end
+function indexed_heap:pop()
+	local priority, id = _heap_pop(self.priorities, self.ids, self.index);
+	if id then
+		local item = self.items[id];
+		self.items[id] = nil;
+		return priority, item, id;
+	end
+end
+function indexed_heap:peek()
+	return self.priorities[1];
+end
+function indexed_heap:reprioritize(id, priority)
+	local k = self.index[id];
+	if k == nil then return; end
+	self.priorities[k] = priority;
+
+	k = _percolate_up(self.priorities, k, self.ids, self.index);
+	k = _percolate_down(self.priorities, k, self.ids, self.index);
+end
+function indexed_heap:remove_index(k)
+	local result = self.priorities[k];
+	if result == nil then return; end
+
+	local result_sync = self.ids[k];
+	local item = self.items[result_sync];
+	local size = #self.priorities;
+
+	self.priorities[k] = self.priorities[size];
+	self.ids[k] = self.ids[size];
+	self.index[self.ids[k]] = k;
+
+	t_remove(self.priorities);
+	t_remove(self.ids);
+
+	self.index[result_sync] = nil;
+	self.items[result_sync] = nil;
+
+	if size > k then
+		k = _percolate_up(self.priorities, k, self.ids, self.index);
+		k = _percolate_down(self.priorities, k, self.ids, self.index);
+	end
+
+	return result, item, result_sync;
+end
+function indexed_heap:remove(id)
+	return self:remove_index(self.index[id]);
+end
+
+local mt = { __index = indexed_heap };
+
+local _M = {
+	create = function()
+		return setmetatable({
+			ids = {}; -- heap of ids, sync'd with priorities
+			items = {}; -- map id->items
+			priorities = {}; -- heap of priorities
+			index = {}; -- map of id->index of id in ids
+			current_id = 1.5
+		}, mt);
+	end
+};
+return _M;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/paths.lua	Fri May 23 20:37:16 2014 +0100
@@ -0,0 +1,38 @@
+local path_sep = package.config:sub(1,1);
+
+local path_util = {}
+
+-- Helper function to resolve relative paths (needed by config)
+function path_util.resolve_relative_path(parent_path, path)
+	if path then
+		-- Some normalization
+		parent_path = parent_path:gsub("%"..path_sep.."+$", "");
+		path = path:gsub("^%.%"..path_sep.."+", "");
+
+		local is_relative;
+		if path_sep == "/" and path:sub(1,1) ~= "/" then
+			is_relative = true;
+		elseif path_sep == "\\" and (path:sub(1,1) ~= "/" and (path:sub(2,3) ~= ":\\" and path:sub(2,3) ~= ":/")) then
+			is_relative = true;
+		end
+		if is_relative then
+			return parent_path..path_sep..path;
+		end
+	end
+	return path;
+end
+
+-- Helper function to convert a glob to a Lua pattern
+function path_util.glob_to_pattern(glob)
+	return "^"..glob:gsub("[%p*?]", function (c)
+		if c == "*" then
+			return ".*";
+		elseif c == "?" then
+			return ".";
+		else
+			return "%"..c;
+		end
+	end).."$";
+end
+
+return path_util;
--- a/util/pluginloader.lua	Wed May 21 13:19:57 2014 -0400
+++ b/util/pluginloader.lua	Fri May 23 20:37:16 2014 +0100
@@ -39,10 +39,10 @@
 	resource = resource or "mod_"..plugin..".lua";
 
 	local names = {
-		"mod_"..plugin.."/"..plugin.."/"..resource; -- mod_hello/hello/mod_hello.lua
-		"mod_"..plugin.."/"..resource;              -- mod_hello/mod_hello.lua
-		plugin.."/"..resource;                      -- hello/mod_hello.lua
-		resource;                                   -- mod_hello.lua
+		"mod_"..plugin..dir_sep..plugin..dir_sep..resource; -- mod_hello/hello/mod_hello.lua
+		"mod_"..plugin..dir_sep..resource;                  -- mod_hello/mod_hello.lua
+		plugin..dir_sep..resource;                          -- hello/mod_hello.lua
+		resource;                                           -- mod_hello.lua
 	};
 
 	return load_file(names);
--- a/util/prosodyctl.lua	Wed May 21 13:19:57 2014 -0400
+++ b/util/prosodyctl.lua	Fri May 23 20:37:16 2014 +0100
@@ -189,8 +189,8 @@
 		return false, "no-pidfile";
 	end
 
-	local modules_enabled = set.new(config.get("*", "modules_enabled"));
-	if not modules_enabled:contains("posix") then
+	local modules_enabled = set.new(config.get("*", "modules_disabled"));
+	if prosody.platform ~= "posix" or modules_enabled:contains("posix") then
 		return false, "no-posix";
 	end
 
--- a/util/sasl.lua	Wed May 21 13:19:57 2014 -0400
+++ b/util/sasl.lua	Fri May 23 20:37:16 2014 +0100
@@ -100,14 +100,16 @@
 function method:mechanisms()
 	local current_mechs = {};
 	for mech, _ in pairs(self.mechs) do
-		if mechanism_channelbindings[mech] and self.profile.cb then
-			local ok = false;
-			for cb_name, _ in pairs(self.profile.cb) do
-				if mechanism_channelbindings[mech][cb_name] then
-					ok = true;
+		if mechanism_channelbindings[mech] then
+			if self.profile.cb then
+				local ok = false;
+				for cb_name, _ in pairs(self.profile.cb) do
+					if mechanism_channelbindings[mech][cb_name] then
+						ok = true;
+					end
 				end
+				if ok == true then current_mechs[mech] = true; end
 			end
-			if ok == true then current_mechs[mech] = true; end
 		else
 			current_mechs[mech] = true;
 		end
--- a/util/timer.lua	Wed May 21 13:19:57 2014 -0400
+++ b/util/timer.lua	Fri May 23 20:37:16 2014 +0100
@@ -6,6 +6,8 @@
 -- COPYING file in the source package for more information.
 --
 
+local indexedbheap = require "util.indexedbheap";
+local log = require "util.logger".init("timer");
 local server = require "net.server";
 local math_min = math.min
 local math_huge = math.huge
@@ -13,6 +15,9 @@
 local t_insert = table.insert;
 local pairs = pairs;
 local type = type;
+local debug_traceback = debug.traceback;
+local tostring = tostring;
+local xpcall = xpcall;
 
 local data = {};
 local new_data = {};
@@ -78,6 +83,61 @@
 	end
 end
 
-add_task = _add_task;
+--add_task = _add_task;
+
+local h = indexedbheap.create();
+local params = {};
+local next_time = nil;
+local _id, _callback, _now, _param;
+local function _call() return _callback(_now, _id, _param); end
+local function _traceback_handler(err) log("error", "Traceback[timer]: %s", debug_traceback(tostring(err), 2)); end
+local function _on_timer(now)
+	local peek;
+	while true do
+		peek = h:peek();
+		if peek == nil or peek > now then break; end
+		local _;
+		_, _callback, _id = h:pop();
+		_now = now;
+		_param = params[_id];
+		params[_id] = nil;
+		--item(now, id, _param); -- FIXME pcall
+		local success, err = xpcall(_call, _traceback_handler);
+		if success and type(err) == "number" then
+			h:insert(_callback, err + now, _id); -- re-add
+			params[_id] = _param;
+		end
+	end
+	next_time = peek;
+	if peek ~= nil then
+		return peek - now;
+	end
+end
+function add_task(delay, callback, param)
+	local current_time = get_time();
+	local event_time = current_time + delay;
+
+	local id = h:insert(callback, event_time);
+	params[id] = param;
+	if next_time == nil or event_time < next_time then
+		next_time = event_time;
+		_add_task(next_time - current_time, _on_timer);
+	end
+	return id;
+end
+function stop(id)
+	params[id] = nil;
+	return h:remove(id);
+end
+function reschedule(id, delay)
+	local current_time = get_time();
+	local event_time = current_time + delay;
+	h:reprioritize(id, delay);
+	if next_time == nil or event_time < next_time then
+		next_time = event_time;
+		_add_task(next_time - current_time, _on_timer);
+	end
+	return id;
+end
 
 return _M;
--- a/util/x509.lua	Wed May 21 13:19:57 2014 -0400
+++ b/util/x509.lua	Fri May 23 20:37:16 2014 +0100
@@ -20,11 +20,9 @@
 
 local nameprep = require "util.encodings".stringprep.nameprep;
 local idna_to_ascii = require "util.encodings".idna.to_ascii;
+local base64 = require "util.encodings".base64;
 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"
 
@@ -214,4 +212,23 @@
 	return false
 end
 
+local pat = "%-%-%-%-%-BEGIN ([A-Z ]+)%-%-%-%-%-\r?\n"..
+"([0-9A-Za-z+/=\r\n]*)\r?\n%-%-%-%-%-END %1%-%-%-%-%-";
+
+function pem2der(pem)
+	local typ, data = pem:match(pat);
+	if typ and data then
+		return base64.decode(data), typ;
+	end
+end
+
+local wrap = ('.'):rep(64);
+local envelope = "-----BEGIN %s-----\n%s\n-----END %s-----\n"
+
+function der2pem(data, typ)
+	typ = typ and typ:upper() or "CERTIFICATE";
+	data = base64.encode(data);
+	return s_format(envelope, typ, data:gsub(wrap, '%0\n', (#data-1)/64), typ);
+end
+
 return _M;
--- a/util/xmppstream.lua	Wed May 21 13:19:57 2014 -0400
+++ b/util/xmppstream.lua	Fri May 23 20:37:16 2014 +0100
@@ -6,7 +6,6 @@
 -- COPYING file in the source package for more information.
 --
 
-
 local lxp = require "lxp";
 local st = require "util.stanza";
 local stanza_mt = st.stanza_mt;
@@ -20,6 +19,10 @@
 
 -- COMPAT: w/LuaExpat 1.1.0
 local lxp_supports_doctype = pcall(lxp.new, { StartDoctypeDecl = false });
+local lxp_supports_xmldecl = pcall(lxp.new, { XmlDecl = false });
+local lxp_supports_bytecount = not not lxp.new({}).getcurrentbytecount;
+
+local default_stanza_size_limit = 1024*1024*10; -- 10MB
 
 module "xmppstream"
 
@@ -40,13 +43,16 @@
 _M.ns_separator = ns_separator;
 _M.ns_pattern = ns_pattern;
 
-function new_sax_handlers(session, stream_callbacks)
+local function dummy_cb() end
+
+function new_sax_handlers(session, stream_callbacks, cb_handleprogress)
 	local xml_handlers = {};
 
 	local cb_streamopened = stream_callbacks.streamopened;
 	local cb_streamclosed = stream_callbacks.streamclosed;
 	local cb_error = stream_callbacks.error or function(session, e, stanza) error("XML stream error: "..tostring(e)..(stanza and ": "..tostring(stanza) or ""),2); end;
 	local cb_handlestanza = stream_callbacks.handlestanza;
+	cb_handleprogress = cb_handleprogress or dummy_cb;
 
 	local stream_ns = stream_callbacks.stream_ns or xmlns_streams;
 	local stream_tag = stream_callbacks.stream_tag or "stream";
@@ -59,6 +65,7 @@
 
 	local stack = {};
 	local chardata, stanza = {};
+	local stanza_size = 0;
 	local non_streamns_depth = 0;
 	function xml_handlers:StartElement(tagname, attr)
 		if stanza and #chardata > 0 then
@@ -87,10 +94,17 @@
 		end
 
 		if not stanza then --if we are not currently inside a stanza
+			if lxp_supports_bytecount then
+				stanza_size = self:getcurrentbytecount();
+			end
 			if session.notopen then
 				if tagname == stream_tag then
 					non_streamns_depth = 0;
 					if cb_streamopened then
+						if lxp_supports_bytecount then
+							cb_handleprogress(stanza_size);
+							stanza_size = 0;
+						end
 						cb_streamopened(session, attr);
 					end
 				else
@@ -105,6 +119,9 @@
 
 			stanza = setmetatable({ name = name, attr = attr, tags = {} }, stanza_mt);
 		else -- we are inside a stanza, so add a tag
+			if lxp_supports_bytecount then
+				stanza_size = stanza_size + self:getcurrentbytecount();
+			end
 			t_insert(stack, stanza);
 			local oldstanza = stanza;
 			stanza = setmetatable({ name = name, attr = attr, tags = {} }, stanza_mt);
@@ -112,12 +129,45 @@
 			t_insert(oldstanza.tags, stanza);
 		end
 	end
+	if lxp_supports_xmldecl then
+		function xml_handlers:XmlDecl(version, encoding, standalone)
+			if lxp_supports_bytecount then
+				cb_handleprogress(self:getcurrentbytecount());
+			end
+		end
+	end
+	function xml_handlers:StartCdataSection()
+		if lxp_supports_bytecount then
+			if stanza then
+				stanza_size = stanza_size + self:getcurrentbytecount();
+			else
+				cb_handleprogress(self:getcurrentbytecount());
+			end
+		end
+	end
+	function xml_handlers:EndCdataSection()
+		if lxp_supports_bytecount then
+			if stanza then
+				stanza_size = stanza_size + self:getcurrentbytecount();
+			else
+				cb_handleprogress(self:getcurrentbytecount());
+			end
+		end
+	end
 	function xml_handlers:CharacterData(data)
 		if stanza then
+			if lxp_supports_bytecount then
+				stanza_size = stanza_size + self:getcurrentbytecount();
+			end
 			t_insert(chardata, data);
+		elseif lxp_supports_bytecount then
+			cb_handleprogress(self:getcurrentbytecount());
 		end
 	end
 	function xml_handlers:EndElement(tagname)
+		if lxp_supports_bytecount then
+			stanza_size = stanza_size + self:getcurrentbytecount()
+		end
 		if non_streamns_depth > 0 then
 			non_streamns_depth = non_streamns_depth - 1;
 		end
@@ -129,6 +179,10 @@
 			end
 			-- Complete stanza
 			if #stack == 0 then
+				if lxp_supports_bytecount then
+					cb_handleprogress(stanza_size);
+				end
+				stanza_size = 0;
 				if tagname ~= stream_error_tag then
 					cb_handlestanza(session, stanza);
 				else
@@ -159,7 +213,7 @@
 	xml_handlers.ProcessingInstruction = restricted_handler;
 
 	local function reset()
-		stanza, chardata = nil, {};
+		stanza, chardata, stanza_size = nil, {}, 0;
 		stack = {};
 	end
 
@@ -170,19 +224,58 @@
 	return xml_handlers, { reset = reset, set_session = set_session };
 end
 
-function new(session, stream_callbacks)
-	local handlers, meta = new_sax_handlers(session, stream_callbacks);
-	local parser = new_parser(handlers, ns_separator);
+function new(session, stream_callbacks, stanza_size_limit)
+	-- Used to track parser progress (e.g. to enforce size limits)
+	local n_outstanding_bytes = 0;
+	local handle_progress;
+	if lxp_supports_bytecount then
+		function handle_progress(n_parsed_bytes)
+			n_outstanding_bytes = n_outstanding_bytes - n_parsed_bytes;
+		end
+		stanza_size_limit = stanza_size_limit or default_stanza_size_limit;
+	elseif stanza_size_limit then
+		error("Stanza size limits are not supported on this version of LuaExpat")
+	end
+
+	local handlers, meta = new_sax_handlers(session, stream_callbacks, handle_progress);
+	local parser = new_parser(handlers, ns_separator, false);
 	local parse = parser.parse;
 
+	function session.open_stream(session, from, to)
+		local send = session.sends2s or session.send;
+
+		local attr = {
+			["xmlns:stream"] = "http://etherx.jabber.org/streams",
+			["xml:lang"] = "en",
+			xmlns = stream_callbacks.default_ns,
+			version = session.version and (session.version > 0 and "1.0" or nil),
+			id = session.streamid or "",
+			from = from or session.host, to = to,
+		};
+		if session.stream_attrs then
+			session:stream_attrs(from, to, attr)
+		end
+		send("<?xml version='1.0'?>");
+		send(st.stanza("stream:stream", attr):top_tag());
+		return true;
+	end
+
 	return {
 		reset = function ()
-			parser = new_parser(handlers, ns_separator);
+			parser = new_parser(handlers, ns_separator, false);
 			parse = parser.parse;
+			n_outstanding_bytes = 0;
 			meta.reset();
 		end,
 		feed = function (self, data)
-			return parse(parser, data);
+			if lxp_supports_bytecount then
+				n_outstanding_bytes = n_outstanding_bytes + #data;
+			end
+			local ok, err = parse(parser, data);
+			if lxp_supports_bytecount and n_outstanding_bytes > stanza_size_limit then
+				return nil, "stanza-too-large";
+			end
+			return ok, err;
 		end,
 		set_session = meta.set_session;
 	};