Merge with main branch.

Sun, 05 Jul 2009 19:05:25 +0200

author
Tobias Markmann <tm@ayena.de>
date
Sun, 05 Jul 2009 19:05:25 +0200
changeset 1487
66f18c18befa
parent 1486
3e04efa8af7e (current diff)
parent 1483
efd19cdda6ca (diff)
child 1489
4da0131a5ccb

Merge with main branch.

--- a/Makefile	Sun Jul 05 19:05:03 2009 +0200
+++ b/Makefile	Sun Jul 05 19:05:25 2009 +0200
@@ -6,6 +6,7 @@
 MODULES = $(DESTDIR)$(PREFIX)/lib/prosody/modules
 SOURCE = $(DESTDIR)$(PREFIX)/lib/prosody
 DATA = $(DESTDIR)$(DATADIR)
+MAN = $(DESTDIR)$(PREFIX)/share/man
 
 INSTALLEDSOURCE = $(PREFIX)/lib/prosody
 INSTALLEDCONFIG = $(SYSCONFDIR)
@@ -18,16 +19,19 @@
 install: prosody.install prosodyctl.install prosody.cfg.lua.install util/encodings.so util/encodings.so util/pposix.so util/signal.so
 	install -d $(BIN) $(CONFIG) $(MODULES) $(SOURCE)
 	install -m750 -d $(DATA)
+	install -d $(MAN)/man1
 	install -d $(CONFIG)/certs
-	install -d $(SOURCE)/core $(SOURCE)/net $(SOURCE)/util
+	install -d $(SOURCE)/core $(SOURCE)/net $(SOURCE)/util $(SOURCE)/fallbacks
 	install -m755 ./prosody.install $(BIN)/prosody
 	install -m755 ./prosodyctl.install $(BIN)/prosodyctl
 	install -m644 core/* $(SOURCE)/core
 	install -m644 net/* $(SOURCE)/net
 	install -m644 util/* $(SOURCE)/util
+	install -m644 fallbacks/* $(SOURCE)/fallbacks
 	install -m644 plugins/* $(MODULES)
 	install -m644 certs/* $(CONFIG)/certs
 	install -m644 plugins/* $(MODULES)
+	install -m644 man/prosodyctl.man $(MAN)/man1/prosodyctl.1
 	test -e $(CONFIG)/prosody.cfg.lua || install -m644 prosody.cfg.lua.install $(CONFIG)/prosody.cfg.lua
 	test -e prosody.version && install prosody.version $(SOURCE)/prosody.version || true
 	$(MAKE) install -C util-src
--- a/core/hostmanager.lua	Sun Jul 05 19:05:03 2009 +0200
+++ b/core/hostmanager.lua	Sun Jul 05 19:05:25 2009 +0200
@@ -27,7 +27,12 @@
 eventmanager.add_event_hook("server-starting", load_enabled_hosts);
 
 function activate(host, host_config)
-	hosts[host] = {type = "local", connected = true, sessions = {}, host = host, s2sout = {}, events = events_new() };
+	hosts[host] = {type = "local", connected = true, sessions = {}, 
+	               host = host, s2sout = {}, events = events_new(), 
+	               disallow_s2s = configmanager.get(host, "core", "disallow_s2s") 
+	                 or (configmanager.get(host, "core", "anonymous_login") 
+	                     and (configmanager.get(host, "core", "disallow_s2s") ~= false))
+	              };
 	log((hosts_loaded_once and "info") or "debug", "Activated host: %s", host);
 	eventmanager.fire_event("host-activated", host, host_config);
 end
--- a/core/s2smanager.lua	Sun Jul 05 19:05:03 2009 +0200
+++ b/core/s2smanager.lua	Sun Jul 05 19:05:25 2009 +0200
@@ -238,7 +238,7 @@
 	conn:settimeout(0);
 	local success, err = conn:connect(connect_host, connect_port);
 	if not success and err ~= "timeout" then
-		log("warn", "s2s connect() failed: %s", err);
+		log("warn", "s2s connect() to %s (%s:%d) failed: %s", host_session.to_host, connect_host, connect_port, err);
 		return false;
 	end
 	
@@ -253,7 +253,7 @@
 	local w = conn.write;
 	host_session.sends2s = function (t) log("debug", "sending: %s", tostring(t)); w(tostring(t)); end
 	
-	conn.write(format([[<stream:stream xmlns='jabber:server' xmlns:db='jabber:server:dialback' xmlns:stream='http://etherx.jabber.org/streams' from='%s' to='%s' version='1.0'>]], from_host, to_host));
+	conn.write(format([[<stream:stream xmlns='jabber:server' xmlns:db='jabber:server:dialback' xmlns:stream='http://etherx.jabber.org/streams' from='%s' to='%s' version='1.0' xml:lang='en'>]], from_host, to_host));
 	log("debug", "Connection attempt in progress...");
 	return true;
 end
--- a/core/sessionmanager.lua	Sun Jul 05 19:05:03 2009 +0200
+++ b/core/sessionmanager.lua	Sun Jul 05 19:05:25 2009 +0200
@@ -40,7 +40,7 @@
 local open_sessions = 0;
 
 function new_session(conn)
-	local session = { conn = conn,  priority = 0, type = "c2s_unauthed", conntime = gettime() };
+	local session = { conn = conn, type = "c2s_unauthed", conntime = gettime() };
 	if true then
 		session.trace = newproxy(true);
 		getmetatable(session.trace).__gc = function () open_sessions = open_sessions - 1; end;
@@ -56,17 +56,9 @@
 function destroy_session(session, err)
 	(session.log or log)("info", "Destroying session for %s (%s@%s)", session.full_jid or "(unknown)", session.username or "(unknown)", session.host or "(unknown)");
 	
-	-- Send unavailable presence
-	if session.presence then
-		local pres = st.presence{ type = "unavailable" };
-		if (not err) or err == "closed" then err = "connection closed"; end
-		pres:tag("status"):text("Disconnected: "..err):up();
-		session:dispatch_stanza(pres);
-	end
-	
 	-- Remove session/resource from user's session list
 	if session.full_jid then
-		hosts[session.host].events.fire_event("resource-unbind", session);
+		hosts[session.host].events.fire_event("resource-unbind", {session=session, error=err});
 
 		hosts[session.host].sessions[session.username].sessions[session.resource] = nil;
 		full_sessions[session.full_jid] = nil;
@@ -132,6 +124,7 @@
 				};
 				if not next(sessions) then
 					hosts[session.host].sessions[session.username] = { sessions = sessions };
+					bare_sessions[session.username.."@"..session.host] = hosts[session.host].sessions[session.username];
 				end
 			end
 			if increment and sessions[resource] then
@@ -151,7 +144,7 @@
 	
 	session.roster = rm_load_roster(session.username, session.host);
 	
-	hosts[session.host].events.fire_event("resource-bind", session);
+	hosts[session.host].events.fire_event("resource-bind", {session=session});
 	
 	return true;
 end
@@ -165,7 +158,7 @@
 	(session.log or session)("debug", "Client sent opening <stream:stream> to %s", session.host);
 	
 	send("<?xml version='1.0'?>");
-	send(format("<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='%s' from='%s' version='1.0'>", session.streamid, session.host));
+	send(format("<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='%s' from='%s' version='1.0' xml:lang='en'>", session.streamid, session.host));
 
 	if not hosts[session.host] then
 		-- We don't serve this host...
--- a/core/stanza_router.lua	Sun Jul 05 19:05:03 2009 +0200
+++ b/core/stanza_router.lua	Sun Jul 05 19:05:25 2009 +0200
@@ -173,12 +173,16 @@
 		core_post_stanza(origin, stanza);
 	elseif origin.type == "c2s" then
 		-- Remote host
-		local xmlns = stanza.attr.xmlns;
-		--stanza.attr.xmlns = "jabber:server";
-		stanza.attr.xmlns = nil;
-		log("debug", "sending s2s stanza: %s", tostring(stanza));
-		send_s2s(origin.host, host, stanza); -- TODO handle remote routing errors
-		stanza.attr.xmlns = xmlns; -- reset
+		if not hosts[from_host].disallow_s2s then
+			local xmlns = stanza.attr.xmlns;
+			--stanza.attr.xmlns = "jabber:server";
+			stanza.attr.xmlns = nil;
+			log("debug", "sending s2s stanza: %s", tostring(stanza));
+			send_s2s(origin.host, host, stanza); -- TODO handle remote routing errors
+			stanza.attr.xmlns = xmlns; -- reset
+		else
+			core_route_stanza(hosts[from_host], st.error_reply(stanza, "cancel", "not-allowed", "Communication with remote servers is not allowed"));
+		end
 	elseif origin.type == "component" or origin.type == "local" then
 		-- Route via s2s for components and modules
 		log("debug", "Routing outgoing stanza for %s to %s", from_host, host);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/man/prosodyctl.man	Sun Jul 05 19:05:25 2009 +0200
@@ -0,0 +1,79 @@
+.TH PROSODYCTL 1 "2009-07-02"
+
+.SH NAME
+prosodyctl \- Manage a Prosody XMPP server
+
+.SH SYNOPSIS
+\fBprosodyctl\fP \fIcommand\fP [\fI--help\fP]
+
+.SH DESCRIPTION
+\fBprosodyctl\fP is the control tool for the Prosody XMPP server. It may be
+used to control the server daemon and manage users.
+
+\fBprosodyctl\fP needs to be executed with sufficient privileges to perform
+its commands. This typically means executing \fBprosodyctl\fP as the root user.
+If a user named "prosody" is found then \fBprosodyctl\fP will change to that
+user before executing its commands.
+
+.SH COMMANDS
+.SS User Management
+In the following commands users are identified by a Jabber ID, \fIjid\fP, of the
+usual form: user@domain.
+
+.IP "\fBadduser\fP \fIjid\fP"
+Adds a user with Jabber ID, \fIjid\fP, to the server. You will be
+prompted to enter the user's password.
+
+.IP "\fBpasswd\fP \fIjid\fP"
+Changes the password of an existing user with Jabber ID, \fIjid\fP. You will be
+prompted to enter the user's new password.
+
+.IP "\fBdeluser\fP \fIjid\fP"
+Deletes an existing user with Jabber ID, \fIjid\fP, from the server.
+
+.SS Daemon Management
+Although \fBprosodyctl\fP has commands to manage the \fBprosody\fP daemon it is
+recommended that you utilize your distributions daemon management features if
+you attained Prosody through a package.
+
+To perform daemon control commands \fBprosodyctl\fP needs a \fIpidfile\fP value
+specified in \fI/etc/prosody/prosody.cfg.lua\fP. Failure to do so will cause
+\fBprosodyctl\fP to complain.
+
+.IP \fBstart\fP
+Starts the \fBprosody\fP server daemon. If run as root \fBprosodyctl\fP will
+attempt to change to a user named "prosody" before executing. This operation
+will block for up to five seconds to wait for the server to execute.
+
+.IP \fBstop\fP
+Stops the \fBprosody\fP server daemon. This operation will block for up to five
+seconds to wait for the server to stop executing.
+
+.IP \fBstatus\fP
+Prints the current execution status of the \fBprosody\fP server daemon.
+
+.SS Ejabberd Compatibility
+\fBejabberd\fP is another XMPP server which provides a comparable control tool,
+\fBejabberdctl\fP, to control its server's operations. \fBprosodyctl\fP
+implements some commands which are compatible with \fBejabberdctl\fP. For
+details of how these commands work you should see
+.BR ejabberdctl (8).
+
+.IP "\fBregister\fP \fIuser server password\fP"
+.IP "\fBunregister\fP \fIuser server\fP"
+
+.SH OPTIONS
+.IP \fI--help\fP
+Display help text for the specified command.
+
+.SH FILES
+.IP \fI/etc/prosody/prosody.cfg.lua\fP
+The main \fBprosody\fP configuration file. \fBprosodyctl\fP reads this to
+determine the process ID file of the \fBprosody\fP server daemon and to
+determine if a host has been configured.
+
+.SH ONLINE
+More information may be found online at: \fIhttp://prosody.im/\fP
+
+.SH AUTHORS
+Dwayne Bent <dbb.0@liqd.org>
--- a/plugins/mod_console.lua	Sun Jul 05 19:05:03 2009 +0200
+++ b/plugins/mod_console.lua	Sun Jul 05 19:05:25 2009 +0200
@@ -437,6 +437,8 @@
 -------------
 
 function printbanner(session)
+	local option = config.get("*", "core", "console_banner");
+if option == nil or option == "full" or option == "graphic" then
 session.print [[
                    ____                \   /     _       
                     |  _ \ _ __ ___  ___  _-_   __| |_   _ 
@@ -446,7 +448,18 @@
                     A study in simplicity            |___/ 
 
 ]]
+end
+if option == nil or option == "short" or option == "full" then
 session.print("Welcome to the Prosody administration console. For a list of commands, type: help");
 session.print("You may find more help on using this console in our online documentation at ");
 session.print("http://prosody.im/doc/console\n");
 end
+if option and option ~= "short" and option ~= "full" and option ~= "graphic" then
+	if type(option) == "string" then
+		session.print(option)
+	elseif type(option) == "function" then
+		setfenv(option, redirect_output(_G, session));
+		pcall(option, session);
+	end
+end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/mod_offline.lua	Sun Jul 05 19:05:25 2009 +0200
@@ -0,0 +1,47 @@
+
+local datamanager = require "util.datamanager";
+local st = require "util.stanza";
+local datetime = require "util.datetime";
+local ipairs = ipairs;
+
+module:add_feature("msgoffline");
+
+module:hook("message/offline/store", function(event)
+	local origin, stanza = event.origin, event.stanza;
+	local to = stanza.attr.to;
+	local node, host;
+	if to then
+		node, host = jid_split(to)
+	else
+		node, host = origin.username, origin.host;
+	end
+	
+	stanza.attr.stamp, stanza.attr.stamp_legacy = datetime.datetime(), datetime.legacy();
+	local result = datamanager.list_append(node, host, "offline", st.preserialize(stanza));
+	stanza.attr.stamp, stanza.attr.stamp_legacy = nil, nil;
+	
+	return true;
+end);
+
+module:hook("message/offline/broadcast", function(event)
+	local origin = event.origin;
+	local node, host = origin.username, origin.host;
+	
+	local data = datamanager.list_load(node, host, "offline");
+	if not data then return true; end
+	for _, stanza in ipairs(data) do
+		stanza = st.deserialize(stanza);
+		stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = host, stamp = stanza.attr.stamp}):up(); -- XEP-0203
+		stanza:tag("x", {xmlns = "jabber:x:delay", from = host, stamp = stanza.attr.stamp_legacy}):up(); -- XEP-0091 (deprecated)
+		stanza.attr.stamp, stanza.attr.stamp_legacy = nil, nil;
+		origin.send(stanza);
+	end
+	return true;
+end);
+
+module:hook("message/offline/delete", function(event)
+	local origin = event.origin;
+	local node, host = origin.username, origin.host;
+
+	return datamanager.list_store(node, host, "offline", nil);
+end);
--- a/plugins/mod_pep.lua	Sun Jul 05 19:05:03 2009 +0200
+++ b/plugins/mod_pep.lua	Sun Jul 05 19:05:25 2009 +0200
@@ -54,8 +54,9 @@
 	local d = data[user];
 	local notify = recipients[user] and recipients[user][recipient];
 	if d and notify then
-		for node, message in pairs(notify) do
-			if d[node] then
+		for node in pairs(notify) do
+			local message = d[node];
+			if message then
 				message.attr.to = recipient;
 				session.send(message);
 			end
@@ -101,7 +102,7 @@
 			recipients[user] = recipients[user] or {};
 			if hash_map[hash] then
 				recipients[user][recipient] = hash_map[hash];
-				publish_all(user, recipient);
+				publish_all(user, recipient, origin);
 			else
 				recipients[user][recipient] = hash;
 				origin.send(
@@ -192,7 +193,7 @@
 			local notify = {};
 			for _, feature in pairs(disco.tags) do
 				if feature.name == "feature" and feature.attr.var then
-					local nfeature = feature.attr.var:match("^(.*)+notify$");
+					local nfeature = feature.attr.var:match("^(.*)%+notify$");
 					if nfeature then notify[nfeature] = true; end
 				end
 			end
--- a/plugins/mod_presence.lua	Sun Jul 05 19:05:03 2009 +0200
+++ b/plugins/mod_presence.lua	Sun Jul 05 19:05:25 2009 +0200
@@ -10,7 +10,7 @@
 
 local require = require;
 local pairs, ipairs = pairs, ipairs;
-local t_concat = table.concat;
+local t_concat, t_insert = table.concat, table.insert;
 local s_find = string.find;
 local tonumber = tonumber;
 
@@ -37,21 +37,6 @@
 	_core_route_stanza(origin, stanza);
 end
 
-function handle_presence(origin, stanza, from_bare, to_bare, core_route_stanza, inbound)
-	local type = stanza.attr.type;
-	if type and type ~= "unavailable" and type ~= "error" then
-		if inbound then
-			handle_inbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare, core_route_stanza);
-		else
-			handle_outbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare, core_route_stanza);
-		end
-	elseif not inbound and not stanza.attr.to then
-		handle_normal_presence(origin, stanza, core_route_stanza);
-	else
-		core_route_stanza(origin, stanza);
-	end
-end
-
 local function select_top_resources(user)
 	local priority = 0;
 	local recipients = {};
@@ -76,93 +61,85 @@
 end
 
 function handle_normal_presence(origin, stanza, core_route_stanza)
-	if origin.roster then
-		for jid in pairs(origin.roster) do -- broadcast to all interested contacts
-			local subscription = origin.roster[jid].subscription;
-			if subscription == "both" or subscription == "from" then
+	local roster = origin.roster;
+	local node, host = origin.username, origin.host;
+	for _, res in pairs(hosts[host].sessions[node].sessions) do -- broadcast to all resources
+		if res ~= origin and res.presence then -- to resource
+			stanza.attr.to = res.full_jid;
+			core_route_stanza(origin, stanza);
+		end
+	end
+	for jid, item in pairs(roster) do -- broadcast to all interested contacts
+		if item.subscription == "both" or item.subscription == "from" then
+			stanza.attr.to = jid;
+			core_route_stanza(origin, stanza);
+		end
+	end
+	if stanza.attr.type == nil and not origin.presence then -- initial presence
+		local probe = st.presence({from = origin.full_jid, type = "probe"});
+		for jid, item in pairs(roster) do -- probe all contacts we are subscribed to
+			if item.subscription == "both" or item.subscription == "to" then
+				probe.attr.to = jid;
+				core_route_stanza(origin, probe);
+			end
+		end
+		for _, res in pairs(hosts[host].sessions[node].sessions) do -- broadcast from all available resources
+			if res ~= origin and res.presence then
+				res.presence.attr.to = origin.full_jid;
+				core_route_stanza(res, res.presence);
+				res.presence.attr.to = nil;
+			end
+		end
+		if roster.pending then -- resend incoming subscription requests
+			for jid in pairs(roster.pending) do
+				origin.send(st.presence({type="subscribe", from=jid})); -- TODO add to attribute? Use original?
+			end
+		end
+		local request = st.presence({type="subscribe", from=origin.username.."@"..origin.host});
+		for jid, item in pairs(roster) do -- resend outgoing subscription requests
+			if item.ask then
+				request.attr.to = jid;
+				core_route_stanza(origin, request);
+			end
+		end
+		local offline = offlinemanager.load(node, host);
+		if offline then
+			for _, msg in ipairs(offline) do
+				origin.send(msg); -- FIXME do we need to modify to/from in any way?
+			end
+			offlinemanager.deleteAll(node, host);
+		end
+	end
+	if stanza.attr.type == "unavailable" then
+		origin.presence = nil;
+		if origin.priority then
+			origin.priority = nil;
+			recalc_resource_map(origin);
+		end
+		if origin.directed then
+			for jid in pairs(origin.directed) do
 				stanza.attr.to = jid;
 				core_route_stanza(origin, stanza);
 			end
-		end
-		local node, host = jid_split(stanza.attr.from);
-		for _, res in pairs(hosts[host].sessions[node].sessions) do -- broadcast to all resources
-			if res ~= origin and res.presence then -- to resource
-				stanza.attr.to = res.full_jid;
-				core_route_stanza(origin, stanza);
-			end
+			origin.directed = nil;
 		end
-		if stanza.attr.type == nil and not origin.presence then -- initial presence
-			local probe = st.presence({from = origin.full_jid, type = "probe"});
-			for jid in pairs(origin.roster) do -- probe all contacts we are subscribed to
-				local subscription = origin.roster[jid].subscription;
-				if subscription == "both" or subscription == "to" then
-					probe.attr.to = jid;
-					core_route_stanza(origin, probe);
-				end
-			end
-			for _, res in pairs(hosts[host].sessions[node].sessions) do -- broadcast from all available resources
-				if res ~= origin and res.presence then
-					res.presence.attr.to = origin.full_jid;
-					core_route_stanza(res, res.presence);
-					res.presence.attr.to = nil;
-				end
-			end
-			if origin.roster.pending then -- resend incoming subscription requests
-				for jid in pairs(origin.roster.pending) do
-					origin.send(st.presence({type="subscribe", from=jid})); -- TODO add to attribute? Use original?
-				end
-			end
-			local request = st.presence({type="subscribe", from=origin.username.."@"..origin.host});
-			for jid, item in pairs(origin.roster) do -- resend outgoing subscription requests
-				if item.ask then
-					request.attr.to = jid;
-					core_route_stanza(origin, request);
-				end
-			end
-			local offline = offlinemanager.load(node, host);
-			if offline then
-				for _, msg in ipairs(offline) do
-					origin.send(msg); -- FIXME do we need to modify to/from in any way?
-				end
-				offlinemanager.deleteAll(node, host);
-			end
+	else
+		origin.presence = stanza;
+		local priority = stanza:child_with_name("priority");
+		if priority and #priority > 0 then
+			priority = t_concat(priority);
+			if s_find(priority, "^[+-]?[0-9]+$") then
+				priority = tonumber(priority);
+				if priority < -128 then priority = -128 end
+				if priority > 127 then priority = 127 end
+			else priority = 0; end
+		else priority = 0; end
+		if origin.priority ~= priority then
+			origin.priority = priority;
+			recalc_resource_map(origin);
 		end
-		if stanza.attr.type == "unavailable" then
-			origin.presence = nil;
-			if origin.priority then
-				origin.priority = nil;
-				recalc_resource_map(origin);
-			end
-			if origin.directed then
-				local old_from = stanza.attr.from;
-				stanza.attr.from = origin.full_jid;
-				for jid in pairs(origin.directed) do
-					stanza.attr.to = jid;
-					core_route_stanza(origin, stanza);
-				end
-				stanza.attr.from = old_from;
-				origin.directed = nil;
-			end
-		else
-			origin.presence = stanza;
-			local priority = stanza:child_with_name("priority");
-			if priority and #priority > 0 then
-				priority = t_concat(priority);
-				if s_find(priority, "^[+-]?[0-9]+$") then
-					priority = tonumber(priority);
-					if priority < -128 then priority = -128 end
-					if priority > 127 then priority = 127 end
-				else priority = 0; end
-			else priority = 0; end
-			if origin.priority ~= priority then
-				origin.priority = priority;
-				recalc_resource_map(origin);
-			end
-		end
-		stanza.attr.to = nil; -- reset it
-	else
-		log("warn", "presence recieved from client with no roster");
 	end
+	stanza.attr.to = nil; -- reset it
 end
 
 function send_presence_of_available_resources(user, host, jid, recipient_session, core_route_stanza)
@@ -268,39 +245,6 @@
 	stanza.attr.from, stanza.attr.to = st_from, st_to;
 end
 
-local function presence_handler(data)
-	local origin, stanza = data.origin, data.stanza;
-	local to = stanza.attr.to;
-	local node, host = jid_split(to);
-	local to_bare = jid_bare(to);
-	local from_bare = jid_bare(stanza.attr.from);
-	if origin.type == "c2s" then
-		if to ~= nil and not(origin.roster[to_bare] and (origin.roster[to_bare].subscription == "both" or origin.roster[to_bare].subscription == "from")) then -- directed presence
-			origin.directed = origin.directed or {};
-			origin.directed[to] = true; -- FIXME does it make more sense to add to_bare rather than to?
-		end
-		if stanza.attr.type ~= nil and stanza.attr.type ~= "unavailable" and stanza.attr.type ~= "error" then
-			handle_outbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare, core_route_stanza);
-		elseif not to then
-			handle_normal_presence(origin, stanza, core_route_stanza);
-		else
-			core_route_stanza(origin, stanza);
-		end
-	elseif (origin.type == "s2sin" or origin.type == "component") and hosts[host] then
-		if stanza.attr.type ~= nil and stanza.attr.type ~= "unavailable" and stanza.attr.type ~= "error" then
-			handle_inbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare, core_route_stanza);
-		else
-			core_route_stanza(origin, stanza);
-		end
-	end
-	return true;
-end
-
-prosody.events.add_handler(module:get_host().."/presence", presence_handler);
-module.unload = function()
-	prosody.events.remove_handler(module:get_host().."/presence", presence_handler);
-end
-
 local outbound_presence_handler = function(data)
 	-- outbound presence recieved
 	local origin, stanza = data.origin, data.stanza;
@@ -371,3 +315,23 @@
 	end -- resource not online, discard
 	return true;
 end);
+
+module:hook("resource-unbind", function(event)
+	local session, err = event.session, event.error;
+	-- Send unavailable presence
+	if session.presence then
+		local pres = st.presence{ type = "unavailable" };
+		if not(err) or err == "closed" then err = "connection closed"; end
+		pres:tag("status"):text("Disconnected: "..err):up();
+		session:dispatch_stanza(pres);
+	elseif session.directed then
+		local pres = st.presence{ type = "unavailable" };
+		if not(err) or err == "closed" then err = "connection closed"; end
+		pres:tag("status"):text("Disconnected: "..err):up();
+		for jid in pairs(session.directed) do
+			pres.attr.to = jid;
+			core_route_stanza(session, pres);
+		end
+		session.directed = nil;
+	end
+end);
--- a/prosody	Sun Jul 05 19:05:03 2009 +0200
+++ b/prosody	Sun Jul 05 19:05:25 2009 +0200
@@ -17,10 +17,13 @@
 -- -- -- -- -- -- -- ---- -- -- -- -- -- -- -- --
 
 if CFG_SOURCEDIR then
-	package.path = CFG_SOURCEDIR.."/?.lua;"..package.path
-	package.cpath = CFG_SOURCEDIR.."/?.so;"..package.cpath
+	package.path = CFG_SOURCEDIR.."/?.lua;"..package.path;
+	package.cpath = CFG_SOURCEDIR.."/?.so;"..package.cpath;
 end
 
+package.path = package.path..";"..(CFG_SOURCEDIR or ".").."/fallbacks/?.lua";
+package.cpath = package.cpath..";"..(CFG_SOURCEDIR or ".").."/fallbacks/?.so";
+
 if CFG_DATADIR then
 	if os.getenv("HOME") then
 		CFG_DATADIR = CFG_DATADIR:gsub("^~", os.getenv("HOME"));
--- a/prosodyctl	Sun Jul 05 19:05:03 2009 +0200
+++ b/prosodyctl	Sun Jul 05 19:05:25 2009 +0200
@@ -95,6 +95,7 @@
 		["unable-to-save-data"] = "Unable to store, perhaps you don't have permission?";
 		["no-pidfile"] = "There is no pidfile option in the configuration file, see http://prosody.im/doc/prosodyctl#pidfile for help";
 		["no-such-method"] = "This module has no commands";
+		["not-running"] = "Prosody is not running";
 		}, { __index = function (t,k) return "Error: "..(tostring(k):gsub("%-", " "):gsub("^.", string.upper)); end });
 
 hosts = {};
@@ -104,6 +105,7 @@
 require "core.modulemanager"
 
 require "util.prosodyctl"
+require "socket"
 -----------------------
 
 function show_message(msg, ...)
@@ -163,6 +165,8 @@
 	end
 	return password;
 end
+
+local prosodyctl_timeout = (config.get("*", "core", "prosodyctl_timeout") or 5) * 2;
 -----------------------
 local commands = {};
 local command = arg[1];
@@ -291,7 +295,24 @@
 	end
 	
 	local ok, ret = prosodyctl.start();
-	if ok then return 0; end
+	if ok then
+		local i=1;
+		while true do
+			local ok, running = prosodyctl.isrunning();
+			if ok and running then
+				break;
+			elseif i == 5 then
+				show_message("Still waiting...");
+			elseif i >= prosodyctl_timeout then
+				show_message("Prosody is still not running. Please give it some time or check your log files for errors.");
+				return 2;
+			end
+			socket.sleep(0.5);
+			i = i + 1;
+		end
+		show_message("Started");
+		return 0;
+	end
 
 	show_message("Failed to start Prosody");
 	show_message(error_messages[ret])	
@@ -344,7 +365,24 @@
 	end
 	
 	local ok, ret = prosodyctl.stop();
-	if ok then return 0; end
+	if ok then
+		local i=1;
+		while true do
+			local ok, running = prosodyctl.isrunning();
+			if ok and not running then
+				break;
+			elseif i == 5 then
+				show_message("Still waiting...");
+			elseif i >= prosodyctl_timeout then
+				show_message("Prosody is still running. Please give it some time or check your log files for errors.");
+				return 2;
+			end
+			socket.sleep(0.5);
+			i = i + 1;
+		end
+		show_message("Stopped");
+		return 0;
+	end
 
 	show_message(error_messages[ret]);
 	return 1;
--- a/util/datamanager.lua	Sun Jul 05 19:05:03 2009 +0200
+++ b/util/datamanager.lua	Sun Jul 05 19:05:25 2009 +0200
@@ -62,7 +62,7 @@
 local function callback(username, host, datastore, data)
 	for _, f in ipairs(callbacks) do
 		username, host, datastore, data = f(username, host, datastore, data);
-		if not username then break; end
+		if username == false then break; end
 	end
 	
 	return username, host, datastore, data;
@@ -123,7 +123,7 @@
 	end
 
 	username, host, datastore, data = callback(username, host, datastore, data);
-	if not username then
+	if username == false then
 		return true; -- Don't save this data at all
 	end
 
@@ -147,7 +147,7 @@
 
 function list_append(username, host, datastore, data)
 	if not data then return; end
-	if callback and callback(username, host, datastore) then return true; end
+	if callback(username, host, datastore) == false then return true; end
 	-- save the datastore
 	local f, msg = io_open(getpath(username, host, datastore, "list", true), "a+");
 	if not f then
@@ -165,7 +165,7 @@
 	if not data then
 		data = {};
 	end
-	if callback and callback(username, host, datastore) then return true; end
+	if callback(username, host, datastore) == false then return true; end
 	-- save the datastore
 	local f, msg = io_open(getpath(username, host, datastore, "list", true), "w+");
 	if not f then
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/hmac.lua	Sun Jul 05 19:05:25 2009 +0200
@@ -0,0 +1,70 @@
+local hashes = require "util.hashes"
+local xor = require "bit".bxor
+
+local t_insert, t_concat = table.insert, table.concat;
+local s_char = string.char;
+
+module "hmac"
+
+local function arraystr(array)
+    local t = {}
+    for i = 1,#array do
+        t_insert(t, s_char(array[i]))
+    end
+
+    return t_concat(t)
+end
+
+--[[
+key
+    the key to use in the hash
+message
+    the message to hash
+hash
+    the hash function
+blocksize
+    the blocksize for the hash function in bytes
+hex
+  return raw hash or hexadecimal string
+--]]
+function hmac(key, message, hash, blocksize, hex)
+    local opad = {}
+    local ipad = {}
+    
+    for i = 1,blocksize do
+        opad[i] = 0x5c
+        ipad[i] = 0x36
+    end
+
+    if #key > blocksize then
+        key = hash(key)
+    end
+
+    for i = 1,#key do
+        ipad[i] = xor(ipad[i],key:sub(i,i):byte())
+        opad[i] = xor(opad[i],key:sub(i,i):byte())
+    end
+
+    opad = arraystr(opad)
+    ipad = arraystr(ipad)
+
+    if hex then
+        return hash(opad..hash(ipad..message), true)
+    else
+        return hash(opad..hash(ipad..message))
+    end
+end
+
+function md5(key, message, hex)
+    return hmac(key, message, hashes.md5, 64, hex)
+end
+
+function sha1(key, message, hex)
+    return hmac(key, message, hashes.sha1, 64, hex)
+end
+
+function sha256(key, message, hex)
+    return hmac(key, message, hashes.sha256, 64, hex)
+end
+
+return _M

mercurial