Merge with tip.

Sun, 29 Nov 2009 21:33:37 +0100

author
Tobias Markmann <tm@ayena.de>
date
Sun, 29 Nov 2009 21:33:37 +0100
changeset 2279
27441b099984
parent 2278
0b0fe49e5251 (current diff)
parent 2263
ff881b857c98 (diff)
child 2280
6f54dac3ec2d

Merge with tip.

doc/lxmppd_core_rostermanager.txt file | annotate | diff | comparison | revisions
doc/lxmppd_core_stanz_dispatch.txt file | annotate | diff | comparison | revisions
net/server.lua file | annotate | diff | comparison | revisions
--- a/.hgtags	Sun Nov 29 21:32:39 2009 +0100
+++ b/.hgtags	Sun Nov 29 21:33:37 2009 +0100
@@ -34,3 +34,5 @@
 e32593074602a785d152f9e153825f29db4d0973 0.5.2
 e32593074602a785d152f9e153825f29db4d0973 0.5.2
 5ae3209fefa2c8dc1c53d08c2c1caa340b8ec542 0.5.2
+1a99a3bf3ce6dbdfb362b7fd101d761fb3cc10af 0.6.0
+81b4e738e4d321b78274132f63a9aec7007e64eb 0.6.1
--- a/Makefile	Sun Nov 29 21:32:39 2009 +0100
+++ b/Makefile	Sun Nov 29 21:33:37 2009 +0100
@@ -26,7 +26,10 @@
 	install -m755 ./prosodyctl.install $(BIN)/prosodyctl
 	install -m644 core/* $(SOURCE)/core
 	install -m644 net/* $(SOURCE)/net
-	install -m644 util/* $(SOURCE)/util
+	install -m644 util/*.lua $(SOURCE)/util
+	install -m644 util/*.so $(SOURCE)/util
+	install -d $(SOURCE)/util/sasl
+	install -m644 util/sasl/* $(SOURCE)/util/sasl
 	install -m644 fallbacks/* $(SOURCE)/fallbacks
 	install -m644 plugins/*.lua $(MODULES)
 	install -d $(MODULES)/muc
--- a/core/loggingmanager.lua	Sun Nov 29 21:32:39 2009 +0100
+++ b/core/loggingmanager.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -17,6 +17,12 @@
 local os_date, os_getenv = os.date, os.getenv;
 local getstyle, getstring = require "util.termcolours".getstyle, require "util.termcolours".getstring;
 
+if os.getenv("__FLUSH_LOG") then
+	local io_flush = io.flush;
+	local _io_write = io_write;
+	io_write = function(...) _io_write(...); io_flush(); end
+end
+
 local config = require "core.configmanager";
 local eventmanager = require "core.eventmanager";
 local logger = require "util.logger";
--- a/core/modulemanager.lua	Sun Nov 29 21:32:39 2009 +0100
+++ b/core/modulemanager.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -28,7 +28,9 @@
 local next = next;
 local rawget = rawget;
 local error = error;
-local tostring = tostring;
+local tostring, tonumber = tostring, tonumber;
+
+local array, set = require "util.array", require "util.set";
 
 local autoload_modules = {"presence", "message", "iq"};
 
@@ -400,6 +402,85 @@
 	return value;
 end
 
+function api:get_option_string(...)
+	local value = self:get_option(...);
+	if type(value) == "table" then
+		if #value > 1 then
+			self:log("error", "Config option '%s' does not take a list, using just the first item", name);
+		end
+		value = value[1];
+	end
+	if value == nil then
+		return nil;
+	end
+	return tostring(value);
+end
+
+function api:get_option_number(name, ...)
+	local value = self:get_option(name, ...);
+	if type(value) == "table" then
+		if #value > 1 then
+			self:log("error", "Config option '%s' does not take a list, using just the first item", name);
+		end
+		value = value[1];
+	end
+	local ret = tonumber(value);
+	if value ~= nil and ret == nil then
+		self:log("error", "Config option '%s' not understood, expecting a number", name);
+	end
+	return ret;
+end
+
+function api:get_option_boolean(name, ...)
+	local value = self:get_option(name, ...);
+	if type(value) == "table" then
+		if #value > 1 then
+			self:log("error", "Config option '%s' does not take a list, using just the first item", name);
+		end
+		value = value[1];
+	end
+	if value == nil then
+		return nil;
+	end
+	local ret = value == true or value == "true" or value == 1 or nil;
+	if ret == nil then
+		ret = (value == false or value == "false" or value == 0);
+		if ret then
+			ret = false;
+		else
+			ret = nil;
+		end
+	end
+	if ret == nil then
+		self:log("error", "Config option '%s' not understood, expecting true/false", name);
+	end
+	return ret;
+end
+
+function api:get_option_array(name, ...)
+	local value = self:get_option(name, ...);
+
+	if value == nil then
+		return nil;
+	end
+	
+	if type(value) ~= "table" then
+		return array{ value }; -- Assume any non-list is a single-item list
+	end
+	
+	return array():append(value); -- Clone
+end
+
+function api:get_option_set(name, ...)
+	local value = self:get_option_array(name, ...);
+	
+	if value == nil then
+		return nil;
+	end
+	
+	return set.new(value);
+end
+
 local t_remove = _G.table.remove;
 local module_items = multitable_new();
 function api:add_item(key, value)
--- a/core/objectmanager.lua	Sun Nov 29 21:32:39 2009 +0100
+++ b/core/objectmanager.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -6,63 +6,63 @@
 -- COPYING file in the source package for more information.
 --
 
-
-local new_multitable = require "util.multitable".new;
-local t_insert = table.insert;
-local t_concat = table.concat;
-local tostring = tostring;
-local unpack = unpack;
-local pairs = pairs;
-local error = error;
-local type = type;
-local _G = _G;
-
-local data = new_multitable();
-
-module "objectmanager"
-
-function set(...)
-	return data:set(...);
-end
-function remove(...)
-	return data:remove(...);
-end
-function get(...)
-	return data:get(...);
-end
-
-local function get_path(path)
-	if type(path) == "table" then return path; end
-	local s = {};
-	for part in tostring(path):gmatch("[%w_]+") do
-		t_insert(s, part);
-	end
-	return s;
-end
-
-function get_object(path)
-	path = get_path(path)
-	return data:get(unpack(path)), path;
-end
-function set_object(path, object)
-	path = get_path(path);
-	data:set(unpack(path), object);
-end
-
-data:set("ls", function(_dir)
-	local obj, dir = get_object(_dir);
-	if not obj then error("object not found: " .. t_concat(dir, '/')); end
-	local r = {};
-	if type(obj) == "table" then
-		for key, val in pairs(obj) do
-			r[key] = type(val);
-		end
-	end
-	return r;
-end);
-data:set("get", get_object);
-data:set("set", set_object);
-data:set("echo", function(...) return {...}; end);
-data:set("_G", _G);
-
-return _M;
+
+local new_multitable = require "util.multitable".new;
+local t_insert = table.insert;
+local t_concat = table.concat;
+local tostring = tostring;
+local unpack = unpack;
+local pairs = pairs;
+local error = error;
+local type = type;
+local _G = _G;
+
+local data = new_multitable();
+
+module "objectmanager"
+
+function set(...)
+	return data:set(...);
+end
+function remove(...)
+	return data:remove(...);
+end
+function get(...)
+	return data:get(...);
+end
+
+local function get_path(path)
+	if type(path) == "table" then return path; end
+	local s = {};
+	for part in tostring(path):gmatch("[%w_]+") do
+		t_insert(s, part);
+	end
+	return s;
+end
+
+function get_object(path)
+	path = get_path(path)
+	return data:get(unpack(path)), path;
+end
+function set_object(path, object)
+	path = get_path(path);
+	data:set(unpack(path), object);
+end
+
+data:set("ls", function(_dir)
+	local obj, dir = get_object(_dir);
+	if not obj then error("object not found: " .. t_concat(dir, '/')); end
+	local r = {};
+	if type(obj) == "table" then
+		for key, val in pairs(obj) do
+			r[key] = type(val);
+		end
+	end
+	return r;
+end);
+data:set("get", get_object);
+data:set("set", set_object);
+data:set("echo", function(...) return {...}; end);
+data:set("_G", _G);
+
+return _M;
--- a/core/s2smanager.lua	Sun Nov 29 21:32:39 2009 +0100
+++ b/core/s2smanager.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -36,13 +36,12 @@
 
 local sha256_hash = require "util.hashes".sha256;
 
-local dialback_secret = uuid_gen();
-
 local adns, dns = require "net.adns", require "net.dns";
 local config = require "core.configmanager";
 local connect_timeout = config.get("*", "core", "s2s_timeout") or 60;
 local dns_timeout = config.get("*", "core", "dns_timeout") or 60;
 local max_dns_depth = config.get("*", "core", "dns_max_depth") or 3;
+local dialback_secret = config.get("*", "core", "dialback_secret") or uuid_gen();
 
 incoming_s2s = {};
 _G.prosody.incoming_s2s = incoming_s2s;
@@ -79,6 +78,10 @@
 end
 
 function send_to_host(from_host, to_host, data)
+	if not hosts[from_host] then
+		log("warn", "Attempt to send stanza from %s - a host we don't serve", from_host);
+		return false;
+	end
 	local host = hosts[from_host].s2sout[to_host];
 	if host then
 		-- We have a connection to this host already
@@ -128,7 +131,7 @@
 	open_sessions = open_sessions + 1;
 	local w, log = conn.write, logger_init("s2sin"..tostring(conn):match("[a-f0-9]+$"));
 	session.log = log;
-	session.sends2s = function (t) log("debug", "sending: %s", tostring(t)); w(tostring(t)); end
+	session.sends2s = function (t) log("debug", "sending: %s", t.top_tag and t:top_tag() or t:match("^([^>]*>?)")); w(conn, tostring(t)); end
 	incoming_s2s[session] = true;
 	add_task(connect_timeout, function ()
 		if session.conn ~= conn or
@@ -317,9 +320,9 @@
 	cl.register_outgoing(conn, host_session);
 	
 	local w, log = conn.write, host_session.log;
-	host_session.sends2s = function (t) log("debug", "sending: %s", tostring(t)); w(tostring(t)); end
+	host_session.sends2s = function (t) log("debug", "sending: %s", (t.top_tag and t:top_tag()) or t:match("^[^>]*>?")); w(conn, 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' xml:lang='en'>]], 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...");
 	add_task(connect_timeout, function ()
 		if host_session.conn ~= conn or
--- a/core/sessionmanager.lua	Sun Nov 29 21:32:39 2009 +0100
+++ b/core/sessionmanager.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -10,7 +10,6 @@
 
 local tonumber, tostring = tonumber, tostring;
 local ipairs, pairs, print, next= ipairs, pairs, print, next;
-local collectgarbage = collectgarbage;
 local format = import("string", "format");
 
 local hosts = hosts;
@@ -50,8 +49,8 @@
 	open_sessions = open_sessions + 1;
 	log("debug", "open sessions now: ".. open_sessions);
 	local w = conn.write;
-	session.send = function (t) w(tostring(t)); end
-	session.ip = conn.ip();
+	session.send = function (t) w(conn, tostring(t)); end
+	session.ip = conn:ip();
 	local conn_name = "c2s"..tostring(conn):match("[a-f0-9]+$");
 	session.log = logger.init(conn_name);
 	
@@ -201,22 +200,32 @@
 end
 
 function send_to_available_resources(user, host, stanza)
+	local jid = user.."@"..host;
 	local count = 0;
-	local to = stanza.attr.to;
-	stanza.attr.to = nil;
-	local h = hosts[host];
-	if h and h.type == "local" then
-		local u = h.sessions[user];
-		if u then
-			for k, session in pairs(u.sessions) do
-				if session.presence then
-					session.send(stanza);
-					count = count + 1;
-				end
+	local user = bare_sessions[jid];
+	if user then
+		for k, session in pairs(user.sessions) do
+			if session.presence then
+				session.send(stanza);
+				count = count + 1;
 			end
 		end
 	end
-	stanza.attr.to = to;
+	return count;
+end
+
+function send_to_interested_resources(user, host, stanza)
+	local jid = user.."@"..host;
+	local count = 0;
+	local user = bare_sessions[jid];
+	if user then
+		for k, session in pairs(user.sessions) do
+			if session.interested then
+				session.send(stanza);
+				count = count + 1;
+			end
+		end
+	end
 	return count;
 end
 
--- a/core/stanza_router.lua	Sun Nov 29 21:32:39 2009 +0100
+++ b/core/stanza_router.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -180,7 +180,7 @@
 			local xmlns = stanza.attr.xmlns;
 			--stanza.attr.xmlns = "jabber:server";
 			stanza.attr.xmlns = nil;
-			log("debug", "sending s2s stanza: %s", tostring(stanza));
+			log("debug", "sending s2s stanza: %s", tostring(stanza.top_tag and stanza:top_tag()) or stanza);
 			send_s2s(origin.host, host, stanza); -- TODO handle remote routing errors
 			stanza.attr.xmlns = xmlns; -- reset
 		else
--- a/core/xmlhandlers.lua	Sun Nov 29 21:32:39 2009 +0100
+++ b/core/xmlhandlers.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -50,7 +50,7 @@
 				chardata = {};
 			end
 			local curr_ns,name = tagname:match("^([^\1]*)\1?(.*)$");
-			if not name then
+			if name == "" then
 				curr_ns, name = "", curr_ns;
 			end
 
@@ -63,7 +63,7 @@
 				local k = attr[i];
 				attr[i] = nil;
 				local ns, nm = k:match("^([^\1]*)\1?(.*)$");
-				if ns and nm then
+				if nm ~= "" then
 					ns = ns_prefixes[ns]; 
 					if ns then 
 						attr[ns..":"..nm] = attr[k];
@@ -105,7 +105,7 @@
 		end
 		function xml_handlers:EndElement(tagname)
 			local curr_ns,name = tagname:match("^([^\1]*)\1?(.*)$");
-			if not name then
+			if name == "" then
 				curr_ns, name = "", curr_ns;
 			end
 			if (not stanza) or (#stanza.last_add > 0 and name ~= stanza.last_add[#stanza.last_add].name) then 
--- a/doc/lxmppd_core_rostermanager.txt	Sun Nov 29 21:32:39 2009 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,9 +0,0 @@
-lxmppd -> core -> rostermanager.lua
-	requires	"util.datamanager"
-	module		"rostermanager"
-	
-function log(type, message)
-	logs a message of type "rostermanager"
-
-function getroster(username, host)
-	Retrieves the user's roster from the server and loads it with the datamanager
\ No newline at end of file
--- a/doc/lxmppd_core_stanz_dispatch.txt	Sun Nov 29 21:32:39 2009 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,27 +0,0 @@
-lxmppd -> core -> stanza_dispatch
-	requires	"util.stanza"
-	requires	"core.usermanager"
-	
-function init_stanza_dispatcher(session)
-	Initialises the stanza dispatcher which handles different stanza according
-	to their type and XML namespace, dispatching to required handlers.
-	
-	iq_handlers["jabber:iq:auth"]
-		A list of handlers for "jabber:iq:auth" stanzas -- authentication
-		(request) stanzas.
-		
-		function (stanza)
-			If one of username, password and resource are missing then it ????.
-			If not, then it validates the credentials and replies with the
-			appropriate stanza.
-			
-	iq_handlers["jabber:iq:roster"]
-		A list of handlers for "jabber:iq:roster" stanzas -- roster management
-		
-		function (stanza)
-			Parses the type of stanza for roster management and does what is
-			requested (roster retrieval, etc.)
-	
-	function (stanza)
-		Validates the stanza and calls the required handler
-
--- a/net/adns.lua	Sun Nov 29 21:32:39 2009 +0100
+++ b/net/adns.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -43,33 +43,32 @@
 end
 
 function new_async_socket(sock, resolver)
-	local newconn, peername = {}, "<unknown>";
+	local peername = "<unknown>";
 	local listener = {};
-	function listener.incoming(conn, data)
-		dns.feed(sock, data);
+	local handler = {};
+	function listener.onincoming(conn, data)
+		dns.feed(handler, data);
 	end
-	function listener.disconnect(conn, err)
+	function listener.ondisconnect(conn, err)
 		log("warn", "DNS socket for %s disconnected: %s", peername, err);
 		local servers = resolver.server;
-		if resolver.socketset[newconn.handler] == resolver.best_server and resolver.best_server == #servers then
+		if resolver.socketset[conn] == resolver.best_server and resolver.best_server == #servers then
 			log("error", "Exhausted all %d configured DNS servers, next lookup will try %s again", #servers, servers[1]);
 		end
 		
 		resolver:servfail(conn); -- Let the magic commence
 	end
-	newconn.handler, newconn._socket = server.wrapclient(sock, "dns", 53, listener);
-	if not newconn.handler then
+	handler = server.wrapclient(sock, "dns", 53, listener);
+	if not handler then
 		log("warn", "handler is nil");
 	end
-	if not newconn._socket then
-		log("warn", "socket is nil");
-	end
-	newconn.handler.settimeout = function () end
-	newconn.handler.setsockname = function (_, ...) return sock:setsockname(...); end
-	newconn.handler.setpeername = function (_, ...) peername = (...); local ret = sock:setpeername(...); _.setsend(sock.send); return ret; end
-	newconn.handler.connect = function (_, ...) return sock:connect(...) end	
-	newconn.handler.send = function (_, data) _.write(data); return _.sendbuffer(); end	
-	return newconn.handler;
+	
+	handler.settimeout = function () end
+	handler.setsockname = function (_, ...) return sock:setsockname(...); end
+	handler.setpeername = function (_, ...) peername = (...); local ret = sock:setpeername(...); _:set_send(sock.send); return ret; end
+	handler.connect = function (_, ...) return sock:connect(...) end	
+	handler.send = function (_, data) _:write(data);  return _.sendbuffer and _.sendbuffer(); end	
+	return handler;
 end
 
 dns:socket_wrapper_set(new_async_socket);
--- a/net/connlisteners.lua	Sun Nov 29 21:32:39 2009 +0100
+++ b/net/connlisteners.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -61,9 +61,14 @@
 		end
 	end
 	
-	return server.addserver(h, 
-			(udata and udata.port) or h.default_port or error("Can't start listener "..name.." because no port was specified, and it has no default port", 0), 
-				(udata and udata.interface) or h.default_interface or "*", (udata and udata.mode) or h.default_mode or 1, (udata and udata.ssl) or nil, 99999999, udata and udata.type == "ssl");
+	local interface = (udata and udata.interface) or h.default_interface or "*";
+	local port = (udata and udata.port) or h.default_port or error("Can't start listener "..name.." because no port was specified, and it has no default port", 0);
+	local mode = (udata and udata.mode) or h.default_mode or 1;
+	local ssl = (udata and udata.ssl) or nil;
+	local maxclients = 99999999;
+	local autossl = udata and udata.type == "ssl";
+	
+	return server.addserver(interface, port, h, mode, ssl, autossl);
 end
 
 return _M;
--- a/net/http.lua	Sun Nov 29 21:32:39 2009 +0100
+++ b/net/http.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -152,7 +152,7 @@
 	end
 	
 	req.handler, req.conn = server.wrapclient(socket.tcp(), req.host, req.port or 80, listener, "*a");
-	req.write = req.handler.write;
+	req.write = function (...) return req.handler:write(...); end
 	req.conn:settimeout(0);
 	local ok, err = req.conn:connect(req.host, req.port or 80);
 	if not ok and err ~= "timeout" then
@@ -200,7 +200,7 @@
 function destroy_request(request)
 	if request.conn then
 		request.handler.close()
-		listener.disconnect(request.conn, "closed");
+		listener.ondisconnect(request.conn, "closed");
 	end
 end
 
--- a/net/httpclient_listener.lua	Sun Nov 29 21:32:39 2009 +0100
+++ b/net/httpclient_listener.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -15,7 +15,7 @@
 
 local httpclient = { default_port = 80, default_mode = "*a" };
 
-function httpclient.listener(conn, data)
+function httpclient.onincoming(conn, data)
 	local request = requests[conn];
 
 	if not request then
@@ -28,7 +28,7 @@
 	end
 end
 
-function httpclient.disconnect(conn, err)
+function httpclient.ondisconnect(conn, err)
 	local request = requests[conn];
 	if request then
 		request:reader(nil);
--- a/net/httpserver.lua	Sun Nov 29 21:32:39 2009 +0100
+++ b/net/httpserver.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -209,7 +209,7 @@
 
 function new_request(handler)
 	return { handler = handler, conn = handler.socket, 
-			write = handler.write, state = "request", 
+			write = function (...) return handler:write(...); end, state = "request", 
 			server = http_servers[handler.serverport()],
 			send = send_response,
 			destroy = destroy_request,
@@ -230,7 +230,7 @@
 		end
 		request.handler.close()
 		if request.conn then
-			listener.disconnect(request.handler, "closed");
+			listener.ondisconnect(request.handler, "closed");
 		end
 	end
 end
--- a/net/httpserver_listener.lua	Sun Nov 29 21:32:39 2009 +0100
+++ b/net/httpserver_listener.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -16,7 +16,7 @@
 
 local httpserver = { default_port = 80, default_mode = "*a" };
 
-function httpserver.listener(conn, data)
+function httpserver.onincoming(conn, data)
 	local request = requests[conn];
 
 	if not request then
@@ -34,7 +34,7 @@
 	end
 end
 
-function httpserver.disconnect(conn, err)
+function httpserver.ondisconnect(conn, err)
 	local request = requests[conn];
 	if request and not request.destroyed then
 		request.conn = nil;
--- a/net/server.lua	Sun Nov 29 21:32:39 2009 +0100
+++ b/net/server.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -1,914 +1,33 @@
--- 
--- server.lua by blastbeat of the luadch project
--- Re-used here under the MIT/X Consortium License
--- 
--- Modifications (C) 2008-2009 Matthew Wild, Waqas Hussain
---
-
--- // wrapping luadch stuff // --
-
-local use = function( what )
-    return _G[ what ]
-end
-local clean = function( tbl )
-    for i, k in pairs( tbl ) do
-        tbl[ i ] = nil
-    end
-end
-
-local log, table_concat = require ("util.logger").init("socket"), table.concat;
-local out_put = function (...) return log("debug", table_concat{...}); end
-local out_error = function (...) return log("warn", table_concat{...}); end
-local mem_free = collectgarbage
-
-----------------------------------// DECLARATION //--
-
---// constants //--
-
-local STAT_UNIT = 1    -- byte
-
---// lua functions //--
-
-local type = use "type"
-local pairs = use "pairs"
-local ipairs = use "ipairs"
-local tostring = use "tostring"
-local collectgarbage = use "collectgarbage"
-
---// lua libs //--
-
-local os = use "os"
-local table = use "table"
-local string = use "string"
-local coroutine = use "coroutine"
-
---// lua lib methods //--
-
-local os_time = os.time
-local os_difftime = os.difftime
-local table_concat = table.concat
-local table_remove = table.remove
-local string_len = string.len
-local string_sub = string.sub
-local coroutine_wrap = coroutine.wrap
-local coroutine_yield = coroutine.yield
-
---// extern libs //--
-
-local luasec = select( 2, pcall( require, "ssl" ) )
-local luasocket = require "socket"
-
---// extern lib methods //--
-
-local ssl_wrap = ( luasec and luasec.wrap )
-local socket_bind = luasocket.bind
-local socket_sleep = luasocket.sleep
-local socket_select = luasocket.select
-local ssl_newcontext = ( luasec and luasec.newcontext )
-
---// functions //--
-
-local id
-local loop
-local stats
-local idfalse
-local addtimer
-local closeall
-local addserver
-local getserver
-local wrapserver
-local getsettings
-local closesocket
-local removesocket
-local removeserver
-local changetimeout
-local wrapconnection
-local changesettings
-
---// tables //--
-
-local _server
-local _readlist
-local _timerlist
-local _sendlist
-local _socketlist
-local _closelist
-local _readtimes
-local _writetimes
-
---// simple data types //--
-
-local _
-local _readlistlen
-local _sendlistlen
-local _timerlistlen
-
-local _sendtraffic
-local _readtraffic
-
-local _selecttimeout
-local _sleeptime
-
-local _starttime
-local _currenttime
-
-local _maxsendlen
-local _maxreadlen
-
-local _checkinterval
-local _sendtimeout
-local _readtimeout
-
-local _cleanqueue
-
-local _timer
-
-local _maxclientsperserver
-
-----------------------------------// DEFINITION //--
-
-_server = { }    -- key = port, value = table; list of listening servers
-_readlist = { }    -- array with sockets to read from
-_sendlist = { }    -- arrary with sockets to write to
-_timerlist = { }    -- array of timer functions
-_socketlist = { }    -- key = socket, value = wrapped socket (handlers)
-_readtimes = { }   -- key = handler, value = timestamp of last data reading
-_writetimes = { }   -- key = handler, value = timestamp of last data writing/sending
-_closelist = { }    -- handlers to close
-
-_readlistlen = 0    -- length of readlist
-_sendlistlen = 0    -- length of sendlist
-_timerlistlen = 0    -- lenght of timerlist
-
-_sendtraffic = 0    -- some stats
-_readtraffic = 0
-
-_selecttimeout = 1    -- timeout of socket.select
-_sleeptime = 0    -- time to wait at the end of every loop
-
-_maxsendlen = 51000 * 1024    -- max len of send buffer
-_maxreadlen = 25000 * 1024    -- max len of read buffer
-
-_checkinterval = 1200000    -- interval in secs to check idle clients
-_sendtimeout = 60000    -- allowed send idle time in secs
-_readtimeout = 6 * 60 * 60    -- allowed read idle time in secs
-
-_cleanqueue = false    -- clean bufferqueue after using
-
-_maxclientsperserver = 1000
-
-_maxsslhandshake = 30 -- max handshake round-trips
-----------------------------------// PRIVATE //--
-
-wrapserver = function( listeners, socket, ip, serverport, pattern, sslctx, maxconnections, startssl )    -- this function wraps a server
-
-    maxconnections = maxconnections or _maxclientsperserver
-
-    local connections = 0
-
-    local dispatch, disconnect = listeners.incoming or listeners.listener, listeners.disconnect
-
-    local err
-
-    local ssl = false
-
-    if sslctx then
-        ssl = true
-        if not ssl_newcontext then
-            out_error "luasec not found"
-            ssl = false
-        end
-        if type( sslctx ) ~= "table" then
-            out_error "server.lua: wrong server sslctx"
-            ssl = false
-        end
-        local ctx;
-        ctx, err = ssl_newcontext( sslctx )
-        if not ctx then
-            err = err or "wrong sslctx parameters"
-            local file;
-            file = err:match("^error loading (.-) %(");
-            if file then
-            	if file == "private key" then
-            		file = sslctx.key or "your private key";
-            	elseif file == "certificate" then
-            		file = sslctx.certificate or "your certificate file";
-            	end
-	        local reason = err:match("%((.+)%)$") or "some reason";
-	        if reason == "Permission denied" then
-	        	reason = "Check that the permissions allow Prosody to read this file.";
-	        elseif reason == "No such file or directory" then
-	        	reason = "Check that the path is correct, and the file exists.";
-	        elseif reason == "system lib" then
-	        	reason = "Previous error (see logs), or other system error.";
-	        else
-	        	reason = "Reason: "..tostring(reason or "unknown"):lower();
-	        end
-	        log("error", "SSL/TLS: Failed to load %s: %s", file, reason);
-	    else
-                log("error", "SSL/TLS: Error initialising for port %d: %s", serverport, err );
-            end
-            ssl = false
-        end
-        sslctx = ctx;
-    end
-    if not ssl then
-      sslctx = false;
-      if startssl then
-         log("error", "Failed to listen on port %d due to SSL/TLS to SSL/TLS initialisation errors (see logs)", serverport )
-         return nil, "Cannot start ssl,  see log for details"
-       end
-    end
-
-    local accept = socket.accept
-
-    --// public methods of the object //--
-
-    local handler = { }
-
-    handler.shutdown = function( ) end
-
-    handler.ssl = function( )
-        return ssl
-    end
-    handler.sslctx = function( )
-        return sslctx
-    end
-    handler.remove = function( )
-        connections = connections - 1
-    end
-    handler.close = function( )
-        for _, handler in pairs( _socketlist ) do
-            if handler.serverport == serverport then
-                handler.disconnect( handler, "server closed" )
-                handler.close( true )
-            end
-        end
-        socket:close( )
-        _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )
-        _readlistlen = removesocket( _readlist, socket, _readlistlen )
-        _socketlist[ socket ] = nil
-        handler = nil
-        socket = nil
-        --mem_free( )
-        out_put "server.lua: closed server handler and removed sockets from list"
-    end
-    handler.ip = function( )
-        return ip
-    end
-    handler.serverport = function( )
-        return serverport
-    end
-    handler.socket = function( )
-        return socket
-    end
-    handler.readbuffer = function( )
-        if connections > maxconnections then
-            out_put( "server.lua: refused new client connection: server full" )
-            return false
-        end
-        local client, err = accept( socket )    -- try to accept
-        if client then
-            local ip, clientport = client:getpeername( )
-            client:settimeout( 0 )
-            local handler, client, err = wrapconnection( handler, listeners, client, ip, serverport, clientport, pattern, sslctx, startssl )    -- wrap new client socket
-            if err then    -- error while wrapping ssl socket
-                return false
-            end
-            connections = connections + 1
-            out_put( "server.lua: accepted new client connection from ", tostring(ip), ":", tostring(clientport), " to ", tostring(serverport))
-            return dispatch( handler )
-        elseif err then    -- maybe timeout or something else
-            out_put( "server.lua: error with new client connection: ", tostring(err) )
-            return false
-        end
-    end
-    return handler
-end
-
-wrapconnection = function( server, listeners, socket, ip, serverport, clientport, pattern, sslctx, startssl )    -- this function wraps a client to a handler object
-
-    socket:settimeout( 0 )
-
-    --// local import of socket methods //--
-
-    local send
-    local receive
-    local shutdown
-
-    --// private closures of the object //--
-
-    local ssl
-
-    local dispatch = listeners.incoming or listeners.listener
-    local status = listeners.status
-    local disconnect = listeners.disconnect
-
-    local bufferqueue = { }    -- buffer array
-    local bufferqueuelen = 0    -- end of buffer array
-
-    local toclose
-    local fatalerror
-    local needtls
-
-    local bufferlen = 0
-
-    local noread = false
-    local nosend = false
-
-    local sendtraffic, readtraffic = 0, 0
-
-    local maxsendlen = _maxsendlen
-    local maxreadlen = _maxreadlen
-
-    --// public methods of the object //--
-
-    local handler = bufferqueue    -- saves a table ^_^
-
-    handler.dispatch = function( )
-        return dispatch
-    end
-    handler.disconnect = function( )
-        return disconnect
-    end
-    handler.setlistener = function( listeners )
-        dispatch = listeners.incoming
-        disconnect = listeners.disconnect
-    end
-    handler.getstats = function( )
-        return readtraffic, sendtraffic
-    end
-    handler.ssl = function( )
-        return ssl
-    end
-    handler.sslctx = function ( )
-        return sslctx
-    end
-    handler.send = function( _, data, i, j )
-        return send( socket, data, i, j )
-    end
-    handler.receive = function( pattern, prefix )
-        return receive( socket, pattern, prefix )
-    end
-    handler.shutdown = function( pattern )
-        return shutdown( socket, pattern )
-    end
-    handler.close = function( forced )
-        if not handler then return true; end
-        _readlistlen = removesocket( _readlist, socket, _readlistlen )
-        _readtimes[ handler ] = nil
-        if bufferqueuelen ~= 0 then
-            if not ( forced or fatalerror ) then
-                handler.sendbuffer( )
-                if bufferqueuelen ~= 0 then   -- try again...
-                    if handler then
-                        handler.write = nil    -- ... but no further writing allowed
-                    end
-                    toclose = true
-                    return false
-                end
-            else
-                send( socket, table_concat( bufferqueue, "", 1, bufferqueuelen ), 1, bufferlen )    -- forced send
-            end
-        end
-        if socket then
-          _ = shutdown and shutdown( socket )
-          socket:close( )
-          _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )
-          _socketlist[ socket ] = nil
-          socket = nil
-        else
-          out_put "server.lua: socket already closed"
-        end
-        if handler then
-            _writetimes[ handler ] = nil
-            _closelist[ handler ] = nil
-            handler = nil
-        end
-	if server then
-		server.remove( )
-	end
-        out_put "server.lua: closed client handler and removed socket from list"
-        return true
-    end
-    handler.ip = function( )
-        return ip
-    end
-    handler.serverport = function( )
-        return serverport
-    end
-    handler.clientport = function( )
-        return clientport
-    end
-    local write = function( data )
-        bufferlen = bufferlen + string_len( data )
-        if bufferlen > maxsendlen then
-            _closelist[ handler ] = "send buffer exceeded"   -- cannot close the client at the moment, have to wait to the end of the cycle
-            handler.write = idfalse    -- dont write anymore
-            return false
-        elseif socket and not _sendlist[ socket ] then
-            _sendlistlen = addsocket(_sendlist, socket, _sendlistlen)
-        end
-        bufferqueuelen = bufferqueuelen + 1
-        bufferqueue[ bufferqueuelen ] = data
-        if handler then
-        	_writetimes[ handler ] = _writetimes[ handler ] or _currenttime
-        end
-        return true
-    end
-    handler.write = write
-    handler.bufferqueue = function( )
-        return bufferqueue
-    end
-    handler.socket = function( )
-        return socket
-    end
-    handler.pattern = function( new )
-        pattern = new or pattern
-        return pattern
-    end
-    handler.setsend = function ( newsend )
-        send = newsend or send
-        return send
-    end
-    handler.bufferlen = function( readlen, sendlen )
-        maxsendlen = sendlen or maxsendlen
-        maxreadlen = readlen or maxreadlen
-        return maxreadlen, maxsendlen
-    end
-    handler.lock = function( switch )
-        if switch == true then
-            handler.write = idfalse
-            local tmp = _sendlistlen
-            _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )
-            _writetimes[ handler ] = nil
-            if _sendlistlen ~= tmp then
-                nosend = true
-            end
-            tmp = _readlistlen
-            _readlistlen = removesocket( _readlist, socket, _readlistlen )
-            _readtimes[ handler ] = nil
-            if _readlistlen ~= tmp then
-                noread = true
-            end
-        elseif switch == false then
-            handler.write = write
-            if noread then
-                noread = false
-                _readlistlen = addsocket(_readlist, socket, _readlistlen)
-                _readtimes[ handler ] = _currenttime
-            end
-            if nosend then
-                nosend = false
-                write( "" )
-            end
-        end
-        return noread, nosend
-    end
-    local _readbuffer = function( )    -- this function reads data
-        local buffer, err, part = receive( socket, pattern )    -- receive buffer with "pattern"
-        if not err or ( err == "timeout" or err == "wantread" ) then    -- received something
-            local buffer = buffer or part or ""
-            local len = string_len( buffer )
-            if len > maxreadlen then
-                disconnect( handler, "receive buffer exceeded" )
-                handler.close( true )
-                return false
-            end
-            local count = len * STAT_UNIT
-            readtraffic = readtraffic + count
-            _readtraffic = _readtraffic + count
-            _readtimes[ handler ] = _currenttime
-            --out_put( "server.lua: read data '", buffer:gsub("[^%w%p ]", "."), "', error: ", err )
-            return dispatch( handler, buffer, err )
-        else    -- connections was closed or fatal error
-            out_put( "server.lua: client ", tostring(ip), ":", tostring(clientport), " read error: ", tostring(err) )
-            fatalerror = true
-            disconnect( handler, err )
-	    _ = handler and handler.close( )
-            return false
-        end
-    end
-    local _sendbuffer = function( )    -- this function sends data
-    	local succ, err, byte, buffer, count;
-    	local count;
-    	if socket then
-            buffer = table_concat( bufferqueue, "", 1, bufferqueuelen )
-            succ, err, byte = send( socket, buffer, 1, bufferlen )
-            count = ( succ or byte or 0 ) * STAT_UNIT
-            sendtraffic = sendtraffic + count
-            _sendtraffic = _sendtraffic + count
-            _ = _cleanqueue and clean( bufferqueue )
-            --out_put( "server.lua: sended '", buffer, "', bytes: ", tostring(succ), ", error: ", tostring(err), ", part: ", tostring(byte), ", to: ", tostring(ip), ":", tostring(clientport) )
-        else
-            succ, err, count = false, "closed", 0;
-        end
-        if succ then    -- sending succesful
-            bufferqueuelen = 0
-            bufferlen = 0
-            _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )    -- delete socket from writelist
-            _ = needtls and handler.starttls(true)
-            _writetimes[ handler ] = nil
-	    _ = toclose and handler.close( )
-            return true
-        elseif byte and ( err == "timeout" or err == "wantwrite" ) then    -- want write
-            buffer = string_sub( buffer, byte + 1, bufferlen )    -- new buffer
-            bufferqueue[ 1 ] = buffer    -- insert new buffer in queue
-            bufferqueuelen = 1
-            bufferlen = bufferlen - byte
-            _writetimes[ handler ] = _currenttime
-            return true
-        else    -- connection was closed during sending or fatal error
-            out_put( "server.lua: client ", tostring(ip), ":", tostring(clientport), " write error: ", tostring(err) )
-            fatalerror = true
-            disconnect( handler, err )
-            _ = handler and handler.close( )
-            return false
-        end
-    end
-
-    -- Set the sslctx
-    local handshake;
-    function handler.set_sslctx(new_sslctx)
-        ssl = true
-        sslctx = new_sslctx;
-        local wrote
-        local read
-        handshake = coroutine_wrap( function( client )    -- create handshake coroutine
-                local err
-                for i = 1, _maxsslhandshake do
-                    _sendlistlen = ( wrote and removesocket( _sendlist, client, _sendlistlen ) ) or _sendlistlen
-                    _readlistlen = ( read and removesocket( _readlist, client, _readlistlen ) ) or _readlistlen
-                    read, wrote = nil, nil
-                    _, err = client:dohandshake( )
-                    if not err then
-                        out_put( "server.lua: ssl handshake done" )
-                        handler.readbuffer = _readbuffer    -- when handshake is done, replace the handshake function with regular functions
-                        handler.sendbuffer = _sendbuffer
-                        _ = status and status( handler, "ssl-handshake-complete" )
-                        _readlistlen = addsocket(_readlist, client, _readlistlen)
-                        return true
-                    else
-                       out_put( "server.lua: error during ssl handshake: ", tostring(err) )
-                       if err == "wantwrite" and not wrote then
-                           _sendlistlen = addsocket(_sendlist, client, _sendlistlen)
-                           wrote = true
-                       elseif err == "wantread" and not read then
-                           _readlistlen = addsocket(_readlist, client, _readlistlen)
-                           read = true
-                       else
-                           break;
-                       end
-                       --coroutine_yield( handler, nil, err )    -- handshake not finished
-                       coroutine_yield( )
-                    end
-                end
-                disconnect( handler, "ssl handshake failed" )
-                _ = handler and handler.close( true )    -- forced disconnect
-                return false    -- handshake failed
-            end
-        )
-    end
-    if sslctx then    -- ssl?
-    	handler.set_sslctx(sslctx);
-        if startssl then    -- ssl now?
-            --out_put("server.lua: ", "starting ssl handshake")
-	    local err
-            socket, err = ssl_wrap( socket, sslctx )    -- wrap socket
-            if err then
-                out_put( "server.lua: ssl error: ", tostring(err) )
-                --mem_free( )
-                return nil, nil, err    -- fatal error
-            end
-            socket:settimeout( 0 )
-            handler.readbuffer = handshake
-            handler.sendbuffer = handshake
-            handshake( socket ) -- do handshake
-            if not socket then
-                return nil, nil, "ssl handshake failed";
-            end
-        else
-            -- We're not automatically doing SSL, so we're not secure (yet)
-            ssl = false
-            handler.starttls = function( now )
-                if not now then
-                    --out_put "server.lua: we need to do tls, but delaying until later"
-                    needtls = true
-                    return
-                end
-                --out_put( "server.lua: attempting to start tls on " .. tostring( socket ) )
-                local oldsocket, err = socket
-                socket, err = ssl_wrap( socket, sslctx )    -- wrap socket
-                --out_put( "server.lua: sslwrapped socket is " .. tostring( socket ) )
-                if err then
-                    out_put( "server.lua: error while starting tls on client: ", tostring(err) )
-                    return nil, err    -- fatal error
-                end
-
-                socket:settimeout( 0 )
-
-                -- add the new socket to our system
-
-                send = socket.send
-                receive = socket.receive
-                shutdown = id
-
-                _socketlist[ socket ] = handler
-                _readlistlen = addsocket(_readlist, socket, _readlistlen)
-
-                -- remove traces of the old socket
-
-                _readlistlen = removesocket( _readlist, oldsocket, _readlistlen )
-                _sendlistlen = removesocket( _sendlist, oldsocket, _sendlistlen )
-                _socketlist[ oldsocket ] = nil
-
-                handler.starttls = nil
-                needtls = nil
-                
-                -- Secure now
-                ssl = true
-
-                handler.readbuffer = handshake
-                handler.sendbuffer = handshake
-                handshake( socket )    -- do handshake
-            end
-            handler.readbuffer = _readbuffer
-            handler.sendbuffer = _sendbuffer
-        end
-    else    -- normal connection
-        ssl = false
-        handler.readbuffer = _readbuffer
-        handler.sendbuffer = _sendbuffer
-    end
-
-    send = socket.send
-    receive = socket.receive
-    shutdown = ( ssl and id ) or socket.shutdown
-
-    _socketlist[ socket ] = handler
-    _readlistlen = addsocket(_readlist, socket, _readlistlen)
-
-    return handler, socket
-end
-
-id = function( )
-end
-
-idfalse = function( )
-    return false
-end
-
-addsocket = function( list, socket, len )
-    if not list[ socket ] then
-      len = len + 1
-      list[ len ] = socket
-      list[ socket ] = len
-    end
-    return len;
-end
-
-removesocket = function( list, socket, len )    -- this function removes sockets from a list ( copied from copas )
-    local pos = list[ socket ]
-    if pos then
-        list[ socket ] = nil
-        local last = list[ len ]
-        list[ len ] = nil
-        if last ~= socket then
-            list[ last ] = pos
-            list[ pos ] = last
-        end
-        return len - 1
-    end
-    return len
-end
-
-closesocket = function( socket )
-    _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )
-    _readlistlen = removesocket( _readlist, socket, _readlistlen )
-    _socketlist[ socket ] = nil
-    socket:close( )
-    --mem_free( )
-end
-
-----------------------------------// PUBLIC //--
-
-addserver = function( listeners, port, addr, pattern, sslctx, maxconnections, startssl )    -- this function provides a way for other scripts to reg a server
-    local err
-    --out_put("server.lua: autossl on ", port, " is ", startssl)
-    if type( listeners ) ~= "table" then
-        err = "invalid listener table"
-    end
-    if not type( port ) == "number" or not ( port >= 0 and port <= 65535 ) then
-        err = "invalid port"
-    elseif _server[ port ] then
-        err =  "listeners on port '" .. port .. "' already exist"
-    elseif sslctx and not luasec then
-        err = "luasec not found"
-    end
-    if err then
-        out_error( "server.lua, port ", port, ": ", err )
-        return nil, err
-    end
-    addr = addr or "*"
-    local server, err = socket_bind( addr, port )
-    if err then
-        out_error( "server.lua, port ", port, ": ", err )
-        return nil, err
-    end
-    local handler, err = wrapserver( listeners, server, addr, port, pattern, sslctx, maxconnections, startssl )    -- wrap new server socket
-    if not handler then
-        server:close( )
-        return nil, err
-    end
-    server:settimeout( 0 )
-    _readlistlen = addsocket(_readlist, server, _readlistlen)
-    _server[ port ] = handler
-    _socketlist[ server ] = handler
-    out_put( "server.lua: new server listener on '", addr, ":", port, "'" )
-    return handler
-end
-
-getserver = function ( port )
-	return _server[ port ];
-end
-
-removeserver = function( port )
-    local handler = _server[ port ]
-    if not handler then
-        return nil, "no server found on port '" .. tostring( port ) .. "'"
-    end
-    handler.close( )
-    _server[ port ] = nil
-    return true
-end
-
-closeall = function( )
-    for _, handler in pairs( _socketlist ) do
-        handler.close( )
-        _socketlist[ _ ] = nil
-    end
-    _readlistlen = 0
-    _sendlistlen = 0
-    _timerlistlen = 0
-    _server = { }
-    _readlist = { }
-    _sendlist = { }
-    _timerlist = { }
-    _socketlist = { }
-    --mem_free( )
-end
-
-getsettings = function( )
-    return  _selecttimeout, _sleeptime, _maxsendlen, _maxreadlen, _checkinterval, _sendtimeout, _readtimeout, _cleanqueue, _maxclientsperserver, _maxsslhandshake
-end
-
-changesettings = function( new )
-    if type( new ) ~= "table" then
-        return nil, "invalid settings table"
-    end
-    _selecttimeout = tonumber( new.timeout ) or _selecttimeout
-    _sleeptime = tonumber( new.sleeptime ) or _sleeptime
-    _maxsendlen = tonumber( new.maxsendlen ) or _maxsendlen
-    _maxreadlen = tonumber( new.maxreadlen ) or _maxreadlen
-    _checkinterval = tonumber( new.checkinterval ) or _checkinterval
-    _sendtimeout = tonumber( new.sendtimeout ) or _sendtimeout
-    _readtimeout = tonumber( new.readtimeout ) or _readtimeout
-    _cleanqueue = new.cleanqueue
-    _maxclientsperserver = new._maxclientsperserver or _maxclientsperserver
-    _maxsslhandshake = new._maxsslhandshake or _maxsslhandshake
-    return true
-end
-
-addtimer = function( listener )
-    if type( listener ) ~= "function" then
-        return nil, "invalid listener function"
-    end
-    _timerlistlen = _timerlistlen + 1
-    _timerlist[ _timerlistlen ] = listener
-    return true
-end
-
-stats = function( )
-    return _readtraffic, _sendtraffic, _readlistlen, _sendlistlen, _timerlistlen
-end
-
-local dontstop = true; -- thinking about tomorrow, ...
-
-setquitting = function (quit)
-	dontstop = not quit;
-	return;
-end
-
-loop = function( )    -- this is the main loop of the program
-    while dontstop do
-        local read, write, err = socket_select( _readlist, _sendlist, _selecttimeout )
-        for i, socket in ipairs( write ) do    -- send data waiting in writequeues
-            local handler = _socketlist[ socket ]
-            if handler then
-                handler.sendbuffer( )
-            else
-                closesocket( socket )
-                out_put "server.lua: found no handler and closed socket (writelist)"    -- this should not happen
-            end
-        end
-        for i, socket in ipairs( read ) do    -- receive data
-            local handler = _socketlist[ socket ]
-            if handler then
-                handler.readbuffer( )
-            else
-                closesocket( socket )
-                out_put "server.lua: found no handler and closed socket (readlist)"    -- this can happen
-            end
-        end
-        for handler, err in pairs( _closelist ) do
-            handler.disconnect( )( handler, err )
-            handler.close( true )    -- forced disconnect
-        end
-        clean( _closelist )
-        _currenttime = os_time( )
-        if os_difftime( _currenttime - _timer ) >= 1 then
-            for i = 1, _timerlistlen do
-                _timerlist[ i ]( _currenttime )    -- fire timers
-            end
-            _timer = _currenttime
-        end
-        socket_sleep( _sleeptime )    -- wait some time
-        --collectgarbage( )
-    end
-    return "quitting"
-end
-
---// EXPERIMENTAL //--
-
-local wrapclient = function( socket, ip, serverport, listeners, pattern, sslctx, startssl )
-    local handler = wrapconnection( nil, listeners, socket, ip, serverport, "clientport", pattern, sslctx, startssl )
-    _socketlist[ socket ] = handler
-    _sendlistlen = addsocket(_sendlist, socket, _sendlistlen)
-    return handler, socket
-end
-
-local addclient = function( address, port, listeners, pattern, sslctx, startssl )
-    local client, err = luasocket.tcp( )
-    if err then
-        return nil, err
-    end
-    client:settimeout( 0 )
-    _, err = client:connect( address, port )
-    if err then    -- try again
-        local handler = wrapclient( client, address, port, listeners )
-    else
-        wrapconnection( nil, listeners, client, address, port, "clientport", pattern, sslctx, startssl )
-    end
-end
-
---// EXPERIMENTAL //--
-
-----------------------------------// BEGIN //--
-
-use "setmetatable" ( _socketlist, { __mode = "k" } )
-use "setmetatable" ( _readtimes, { __mode = "k" } )
-use "setmetatable" ( _writetimes, { __mode = "k" } )
-
-_timer = os_time( )
-_starttime = os_time( )
-
-addtimer( function( )
-        local difftime = os_difftime( _currenttime - _starttime )
-        if difftime > _checkinterval then
-            _starttime = _currenttime
-            for handler, timestamp in pairs( _writetimes ) do
-                if os_difftime( _currenttime - timestamp ) > _sendtimeout then
-                    --_writetimes[ handler ] = nil
-                    handler.disconnect( )( handler, "send timeout" )
-                    handler.close( true )    -- forced disconnect
-                end
-            end
-            for handler, timestamp in pairs( _readtimes ) do
-                if os_difftime( _currenttime - timestamp ) > _readtimeout then
-                    --_readtimes[ handler ] = nil
-                    handler.disconnect( )( handler, "read timeout" )
-                    handler.close( )    -- forced disconnect?
-                end
-            end
-        end
-    end
-)
-
-----------------------------------// PUBLIC INTERFACE //--
-
-return {
-
-    addclient = addclient,
-    wrapclient = wrapclient,
-    
-    loop = loop,
-    stats = stats,
-    closeall = closeall,
-    addtimer = addtimer,
-    addserver = addserver,
-    getserver = getserver,
-    getsettings = getsettings,
-    setquitting = setquitting,
-    removeserver = removeserver,
-    changesettings = changesettings,
-}
+
+local use_luaevent = require "core.configmanager".get("*", "core", "use_libevent");
+
+if use_luaevent then
+	use_luaevent = pcall(require, "luaevent.core");
+	if not use_luaevent then
+		log("error", "libevent not found, falling back to select()");
+	end
+end
+
+local server;
+
+if use_luaevent then
+	server = require "net.server_event";
+	-- util.timer requires "net.server", so instead of having
+	-- Lua look for, and load us again (causing a loop) - set this here
+	-- (usually it isn't set until we return, look down there...)
+	package.loaded["net.server"] = server;
+	
+	-- Backwards compatibility for timers, addtimer
+	-- called a function roughly every second
+	local add_task = require "util.timer".add_task;
+	function server.addtimer(f)
+		return add_task(1, function (...) f(...); return 1; end);
+	end
+else
+	server = require "net.server_select";
+	package.loaded["net.server"] = server;
+end
+
+-- require "net.server" shall now forever return this,
+-- ie. server_select or server_event as chosen above.
+return server; 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/net/server_event.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -0,0 +1,786 @@
+--[[
+
+
+			server.lua based on lua/libevent by blastbeat
+
+			notes:
+			-- when using luaevent, never register 2 or more EV_READ at one socket, same for EV_WRITE
+			-- you cant even register a new EV_READ/EV_WRITE callback inside another one
+			-- never call eventcallback:close( ) from inside eventcallback
+			-- to do some of the above, use timeout events or something what will called from outside
+			-- dont let garbagecollect eventcallbacks, as long they are running
+			-- when using luasec, there are 4 cases of timeout errors: wantread or wantwrite during reading or writing
+
+--]]
+
+
+local SCRIPT_NAME           = "server_event.lua"
+local SCRIPT_VERSION        = "0.05"
+local SCRIPT_AUTHOR         = "blastbeat"
+local LAST_MODIFIED         = "2009/11/20"
+
+local cfg = {
+	MAX_CONNECTIONS       = 100000,  -- max per server connections (use "ulimit -n" on *nix)
+	MAX_HANDSHAKE_ATTEMPS = 10,  -- attemps to finish ssl handshake
+	HANDSHAKE_TIMEOUT     = 1,  -- timout in seconds per handshake attemp
+	MAX_READ_LENGTH       = 1024 * 1024 * 1024 * 1024,  -- max bytes allowed to read from sockets
+	MAX_SEND_LENGTH       = 1024 * 1024 * 1024 * 1024,  -- max bytes size of write buffer (for writing on sockets)
+	ACCEPT_DELAY          = 10,  -- seconds to wait until the next attemp of a full server to accept
+	READ_TIMEOUT          = 60 * 30,  -- timeout in seconds for read data from socket
+	WRITE_TIMEOUT         = 30,  -- timeout in seconds for write data on socket
+	CONNECT_TIMEOUT       = 10,  -- timeout in seconds for connection attemps
+	CLEAR_DELAY           = 5,  -- seconds to wait for clearing interface list (and calling ondisconnect listeners) 
+	DEBUG                 = true,  -- show debug messages
+}
+
+local function use(x) return rawget(_G, x); end
+local print = use "print"
+local pcall = use "pcall"
+local ipairs = use "ipairs"
+local string = use "string"
+local select = use "select"
+local require = use "require"
+local tostring = use "tostring"
+local coroutine = use "coroutine"
+local setmetatable = use "setmetatable"
+
+local ssl = use "ssl"
+local socket = use "socket"
+
+local log = require ("util.logger").init("socket")
+
+local function debug(...)
+	return log("debug", ("%s "):rep(select('#', ...)), ...)
+end
+local vdebug = debug;
+
+local bitor = ( function( ) -- thx Rici Lake
+	local hasbit = function( x, p )
+		return x % ( p + p ) >= p
+	end
+	return function( x, y ) 
+		local p = 1
+		local z = 0
+		local limit = x > y and x or y
+		while p <= limit do 
+			if hasbit( x, p ) or hasbit( y, p ) then
+				z = z + p
+			end
+			p = p + p
+		end
+		return z
+	end
+end )( )
+
+local event = require "luaevent.core"
+local base = event.new( )
+local EV_READ = event.EV_READ
+local EV_WRITE = event.EV_WRITE
+local EV_TIMEOUT = event.EV_TIMEOUT
+
+local EV_READWRITE = bitor( EV_READ, EV_WRITE )
+
+local interfacelist = ( function( )  -- holds the interfaces for sockets
+	local array = { }
+	local len = 0
+	return function( method, arg )
+		if "add" == method then
+			len = len + 1
+			array[ len ] = arg
+			arg:_position( len )
+			return len
+		elseif "delete" == method then
+			if len <= 0 then
+				return nil, "array is already empty"
+			end
+			local position = arg:_position()  -- get position in array
+			if position ~= len then
+				local interface = array[ len ]  -- get last interface
+				array[ position ] = interface  -- copy it into free position
+				array[ len ] = nil  -- free last position
+				interface:_position( position )  -- set new position in array
+			else  -- free last position
+				array[ len ] = nil
+			end
+			len = len - 1
+			return len    
+		else
+			return array
+		end
+	end
+end )( )
+
+-- Client interface methods
+local interface_mt
+do
+	interface_mt = {}; interface_mt.__index = interface_mt;
+	
+	local addevent = base.addevent
+	local coroutine_wrap, coroutine_yield = coroutine.wrap,coroutine.yield
+	local string_len = string.len
+	
+	-- Private methods
+	function interface_mt:_position(new_position)
+			self.position = new_position or self.position
+			return self.position;
+	end
+	function interface_mt:_close() -- regs event to start self:_destroy()
+			local callback = function( )
+				self:_destroy();
+				self.eventclose = nil
+				return -1
+			end
+			self.eventclose = addevent( base, nil, EV_TIMEOUT, callback, 0 )
+			return true
+	end
+	
+	function interface_mt:_start_connection(plainssl) -- should be called from addclient
+			local callback = function( event )
+				if EV_TIMEOUT == event then  -- timout during connection
+					self.fatalerror = "connection timeout"
+					self:ontimeout()  -- call timeout listener
+					self:_close()
+					debug( "new connection failed. id:", self.id, "error:", self.fatalerror )
+				else
+					if plainssl then  -- start ssl session
+						self:_start_ssl( self.listener.onconnect )
+					else  -- normal connection
+						self:_start_session( self.listener.onconnect )
+					end
+					debug( "new connection established. id:", self.id )
+				end
+				self.eventconnect = nil
+				return -1
+			end
+			self.eventconnect = addevent( base, self.conn, EV_WRITE, callback, cfg.CONNECT_TIMEOUT )
+			return true
+	end
+	function interface_mt:_start_session(onconnect) -- new session, for example after startssl
+		if self.type == "client" then
+			local callback = function( )
+				self:_lock( false,  false, false )
+				--vdebug( "start listening on client socket with id:", self.id )      
+				self.eventread = addevent( base, self.conn, EV_READ, self.readcallback, cfg.READ_TIMEOUT )  -- register callback
+				self:onconnect()
+				self.eventsession = nil
+				return -1
+			end
+			self.eventsession = addevent( base, nil, EV_TIMEOUT, callback, 0 )
+		else
+			self:_lock( false )
+			--vdebug( "start listening on server socket with id:", self.id )
+			self.eventread = addevent( base, self.conn, EV_READ, self.readcallback )  -- register callback
+		end
+		return true
+	end
+	function interface_mt:_start_ssl(arg) -- old socket will be destroyed, therefore we have to close read/write events first
+			--vdebug( "starting ssl session with client id:", self.id )
+			local _
+			_ = self.eventread and self.eventread:close( )  -- close events; this must be called outside of the event callbacks!
+			_ = self.eventwrite and self.eventwrite:close( )
+			self.eventread, self.eventwrite = nil, nil
+			local err
+			self.conn, err = ssl.wrap( self.conn, self._sslctx )
+			if err then
+				self.fatalerror = err
+				self.conn = nil  -- cannot be used anymore
+				if "onconnect" == arg then
+					self.ondisconnect = nil  -- dont call this when client isnt really connected
+				end
+				self:_close()
+				debug( "fatal error while ssl wrapping:", err )
+				return false
+			end
+			self.conn:settimeout( 0 )  -- set non blocking
+			local handshakecallback = coroutine_wrap(
+				function( event )
+					local _, err
+					local attempt = 0
+					local maxattempt = cfg.MAX_HANDSHAKE_ATTEMPS
+					while attempt < 1000 do  -- no endless loop
+						attempt = attempt + 1
+						debug( "ssl handshake of client with id:"..tostring(self).."attemp:"..attempt )
+						if attempt > maxattempt then
+							self.fatalerror = "max handshake attemps exceeded"
+						elseif EV_TIMEOUT == event then
+							self.fatalerror = "timeout during handshake"
+						else
+							_, err = self.conn:dohandshake( )
+							if not err then
+								self:_lock( false, false, false )  -- unlock the interface; sending, closing etc allowed
+								self.send = self.conn.send  -- caching table lookups with new client object
+								self.receive = self.conn.receive
+								local onsomething
+								if "onconnect" == arg then  -- trigger listener
+									onsomething = self.onconnect
+								else
+									onsomething = self.onsslconnection
+								end
+								self:_start_session( onsomething )
+								debug( "ssl handshake done" )
+								self.eventhandshake = nil
+								return -1
+							end
+							debug( "error during ssl handshake:", err ) 
+							if err == "wantwrite" then
+								event = EV_WRITE
+							elseif err == "wantread" then
+								event = EV_READ
+							else
+								self.fatalerror = err
+							end            
+						end
+						if self.fatalerror then
+							if "onconnect" == arg then
+								self.ondisconnect = nil  -- dont call this when client isnt really connected
+							end
+							self:_close()
+							debug( "handshake failed because:", self.fatalerror )
+							self.eventhandshake = nil
+							return -1
+						end
+						event = coroutine_yield( event, cfg.HANDSHAKE_TIMEOUT )  -- yield this monster...
+					end
+				end
+			)
+			debug "starting handshake..."
+			self:_lock( false, true, true )  -- unlock read/write events, but keep interface locked 
+			self.eventhandshake = addevent( base, self.conn, EV_READWRITE, handshakecallback, cfg.HANDSHAKE_TIMEOUT )
+			return true
+	end
+	function interface_mt:_destroy()  -- close this interface + events and call last listener
+			debug( "closing client with id:", self.id )
+			self:_lock( true, true, true )  -- first of all, lock the interface to avoid further actions
+			local _
+			_ = self.eventread and self.eventread:close( )  -- close events; this must be called outside of the event callbacks!
+			if self.type == "client" then
+				_ = self.eventwrite and self.eventwrite:close( )
+				_ = self.eventhandshake and self.eventhandshake:close( )
+				_ = self.eventstarthandshake and self.eventstarthandshake:close( )
+				_ = self.eventconnect and self.eventconnect:close( )
+				_ = self.eventsession and self.eventsession:close( )
+				_ = self.eventwritetimeout and self.eventwritetimeout:close( )
+				_ = self.eventreadtimeout and self.eventreadtimeout:close( )
+				_ = self.ondisconnect and self:ondisconnect( self.fatalerror )  -- call ondisconnect listener (wont be the case if handshake failed on connect)
+				_ = self.conn and self.conn:close( ) -- close connection, must also be called outside of any socket registered events!
+				_ = self._server and self._server:counter(-1);
+				self.eventread, self.eventwrite = nil, nil
+				self.eventstarthandshake, self.eventhandshake, self.eventclose = nil, nil, nil
+				self.readcallback, self.writecallback = nil, nil
+			else
+				self.conn:close( )
+				self.eventread, self.eventclose = nil, nil
+				self.interface, self.readcallback = nil, nil
+			end
+			interfacelist( "delete", self )
+			return true
+	end
+	
+	function interface_mt:_lock(nointerface, noreading, nowriting)  -- lock or unlock this interface or events
+			self.nointerface, self.noreading, self.nowriting = nointerface, noreading, nowriting
+			return nointerface, noreading, nowriting
+	end
+
+	function interface_mt:counter(c)
+		if c then
+			self._connections = self._connections + c
+		end
+		return self._connections
+	end
+	
+	-- Public methods
+	function interface_mt:write(data)
+		if self.nowriting then return nil, "locked" end
+		--vdebug( "try to send data to client, id/data:", self.id, data )
+		data = tostring( data )
+		local len = string_len( data )
+		local total = len + self.writebufferlen
+		if total > cfg.MAX_SEND_LENGTH then  -- check buffer length
+			local err = "send buffer exceeded"
+			debug( "error:", err )  -- to much, check your app
+			return nil, err
+		end 
+		self.writebuffer = self.writebuffer .. data -- new buffer
+		self.writebufferlen = total
+		if not self.eventwrite then  -- register new write event
+			--vdebug( "register new write event" )
+			self.eventwrite = addevent( base, self.conn, EV_WRITE, self.writecallback, cfg.WRITE_TIMEOUT )
+		end
+		return true
+	end
+	function interface_mt:close(now)
+		if self.nointerface then return nil, "locked"; end
+		debug( "try to close client connection with id:", self.id )
+		if self.type == "client" then
+			self.fatalerror = "client to close"
+			if ( not self.eventwrite ) or now then  -- try to close immediately
+				self:_lock( true, true, true )
+				self:_close()
+				return true
+			else  -- wait for incomplete write request
+				self:_lock( true, true, false )
+				debug "closing delayed until writebuffer is empty"
+				return nil, "writebuffer not empty, waiting"
+			end
+		else
+			debug( "try to close server with id:", self.id, "args:", now )
+			self.fatalerror = "server to close"
+			self:_lock( true )
+			local count = 0
+			for _, item in ipairs( interfacelist( ) ) do
+				if ( item.type ~= "server" ) and ( item._server == self ) then  -- client/server match
+					if item:close( now ) then  -- writebuffer was empty
+						count = count + 1
+					end
+				end
+			end
+			local timeout = 0  -- dont wait for unfinished writebuffers of clients...
+			if not now then
+				timeout = cfg.WRITE_TIMEOUT  -- ...or wait for it
+			end
+			self:_close( timeout )  -- add new event to remove the server interface
+			debug( "seconds remained until server is closed:", timeout )
+			return count  -- returns finished clients with empty writebuffer
+		end
+	end
+	
+	function interface_mt:server()
+		return self._server or self;
+	end
+	
+	function interface_mt:port()
+		return self._port
+	end
+	
+	function interface_mt:ip()
+		return self._ip
+	end
+	
+	function interface_mt:ssl()
+		return self._usingssl
+	end
+
+	function interface_mt:type()
+		return self._type or "client"
+	end
+	
+	function interface_mt:connections()
+		return self._connections
+	end
+	
+	function interface_mt:address()
+		return self.addr
+	end
+	
+	function interface_mt:set_sslctx(sslctx)
+		self._sslctx = sslctx;
+		if sslctx then
+			self.starttls = nil; -- use starttls() of interface_mt
+		else
+			self.starttls = false; -- prevent starttls()
+		end
+	end
+	
+	function interface_mt:set_send(new_send)
+		-- No-op, we always use the underlying connection's send
+	end
+	
+	function interface_mt:starttls()
+		debug( "try to start ssl at client id:", self.id )
+		local err
+		if not self._sslctx then  -- no ssl available
+			err = "no ssl context available"
+		elseif self._usingssl then  -- startssl was already called
+			err = "ssl already active"
+		end
+		if err then
+			debug( "error:", err )
+			return nil, err      
+		end
+		self._usingssl = true
+		self.startsslcallback = function( )  -- we have to start the handshake outside of a read/write event
+			self.startsslcallback = nil
+			self:_start_ssl();
+			self.eventstarthandshake = nil
+			return -1
+		end
+		if not self.eventwrite then
+			self:_lock( true, true, true )  -- lock the interface, to not disturb the handshake
+			self.eventstarthandshake = addevent( base, nil, EV_TIMEOUT, self.startsslcallback, 0 )  -- add event to start handshake
+		else  -- wait until writebuffer is empty
+			self:_lock( true, true, false )
+			debug "ssl session delayed until writebuffer is empty..."
+		end
+		return true
+	end
+	
+	-- Stub handlers
+	function interface_mt:onconnect()
+	end
+	function interface_mt:onincoming()
+	end
+	function interface_mt:ondisconnect()
+	end
+	function interface_mt:ontimeout()
+	end
+end			
+
+-- End of client interface methods
+
+local handleclient;
+do
+	local string_sub = string.sub  -- caching table lookups
+	local string_len = string.len
+	local addevent = base.addevent
+	local coroutine_wrap = coroutine.wrap
+	local socket_gettime = socket.gettime
+	local coroutine_yield = coroutine.yield
+	function handleclient( client, ip, port, server, pattern, listener, _, sslctx )  -- creates an client interface
+		--vdebug("creating client interfacce...")
+		local interface = {
+			type = "client";
+			conn = client;
+			currenttime = socket_gettime( );  -- safe the origin
+			writebuffer = "";  -- writebuffer
+			writebufferlen = 0;  -- length of writebuffer
+			send = client.send;  -- caching table lookups
+			receive = client.receive;
+			onconnect = listener.onconnect;  -- will be called when client disconnects
+			ondisconnect = listener.ondisconnect;  -- will be called when client disconnects
+			onincoming = listener.onincoming;  -- will be called when client sends data
+			ontimeout = listener.ontimeout; -- called when fatal socket timeout occurs
+			eventread = false, eventwrite = false, eventclose = false,
+			eventhandshake = false, eventstarthandshake = false;  -- event handler
+			eventconnect = false, eventsession = false;  -- more event handler...
+			eventwritetimeout = false;  -- even more event handler...
+			eventreadtimeout = false;
+			fatalerror = false;  -- error message
+			writecallback = false;  -- will be called on write events
+			readcallback = false;  -- will be called on read events
+			nointerface = true;  -- lock/unlock parameter of this interface
+			noreading = false, nowriting = false;  -- locks of the read/writecallback
+			startsslcallback = false;  -- starting handshake callback
+			position = false;  -- position of client in interfacelist
+			
+			-- Properties
+			_ip = ip, _port = port, _server = server, _pattern = pattern,
+			_sslctx = sslctx; -- parameters
+			_usingssl = false;  -- client is using ssl;
+		}
+		if not sslctx then
+			interface.starttls = false -- don't allow TLS
+		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 )
+			if interface.nowriting or ( interface.fatalerror and ( "client to close" ~= interface.fatalerror ) ) then  -- leave this event
+				--vdebug( "leaving this event because:", interface.nowriting or interface.fatalerror )
+				interface.eventwrite = false
+				return -1
+			end
+			if EV_TIMEOUT == event then  -- took too long to write some data to socket -> disconnect
+				interface.fatalerror = "timeout during writing"
+				debug( "writing failed:", interface.fatalerror ) 
+				interface:_close()
+				interface.eventwrite = false
+				return -1
+			else  -- can write :)
+				if interface._usingssl then  -- handle luasec
+					if interface.eventreadtimeout then  -- we have to read first
+						local ret = interface.readcallback( )  -- call readcallback
+						--vdebug( "tried to read in writecallback, result:", ret )
+					end
+					if interface.eventwritetimeout then  -- luasec only
+						interface.eventwritetimeout:close( )  -- first we have to close timeout event which where regged after a wantread error
+						interface.eventwritetimeout = false
+					end
+				end
+				local succ, err, byte = interface.conn:send( interface.writebuffer, 1, interface.writebufferlen )
+				--vdebug( "write data:", interface.writebuffer, "error:", err, "part:", byte )
+				if succ then  -- writing succesful
+					interface.writebuffer = ""
+					interface.writebufferlen = 0
+					if interface.fatalerror then
+						debug "closing client after writing"
+						interface:_close()  -- close interface if needed
+					elseif interface.startsslcallback then  -- start ssl connection if needed
+						debug "starting ssl handshake after writing"
+						interface.eventstarthandshake = addevent( base, nil, EV_TIMEOUT, interface.startsslcallback, 0 )
+					elseif interface.eventreadtimeout then
+						return EV_WRITE, EV_TIMEOUT
+					end
+					interface.eventwrite = nil
+					return -1
+				elseif byte then  -- want write again
+					--vdebug( "writebuffer is not empty:", err )
+					interface.writebuffer = string_sub( interface.writebuffer, byte + 1, interface.writebufferlen )  -- new buffer
+					interface.writebufferlen = interface.writebufferlen - byte            
+					if "wantread" == err then  -- happens only with luasec
+						local callback = function( )
+							interface:_close()
+							interface.eventwritetimeout = nil
+							return evreturn, evtimeout
+						end
+						interface.eventwritetimeout = addevent( base, nil, EV_TIMEOUT, callback, cfg.WRITE_TIMEOUT )  -- reg a new timeout event
+						debug( "wantread during write attemp, reg it in readcallback but dont know what really happens next..." )
+						-- hopefully this works with luasec; its simply not possible to use 2 different write events on a socket in luaevent
+						return -1
+					end
+					return EV_WRITE, cfg.WRITE_TIMEOUT 
+				else  -- connection was closed during writing or fatal error
+					interface.fatalerror = err or "fatal error"
+					debug( "connection failed in write event:", interface.fatalerror ) 
+					interface:_close()
+					interface.eventwrite = nil
+					return -1
+				end
+			end
+		end
+		
+		interface.readcallback = function( event )  -- called on read events
+			--vdebug( "new client read event, id/ip/port:", tostring(interface.id), tostring(ip), tostring(port) )
+			if interface.noreading or interface.fatalerror then  -- leave this event
+				--vdebug( "leaving this event because:", tostring(interface.noreading or interface.fatalerror) )
+				interface.eventread = nil
+				return -1
+			end
+			if EV_TIMEOUT == event then  -- took too long to get some data from client -> disconnect
+				interface.fatalerror = "timeout during receiving"
+				debug( "connection failed:", interface.fatalerror ) 
+				interface:_close()
+				interface.eventread = nil
+				return -1
+			else -- can read
+				if interface._usingssl then  -- handle luasec
+					if interface.eventwritetimeout then  -- ok, in the past writecallback was regged
+						local ret = interface.writecallback( )  -- call it
+						--vdebug( "tried to write in readcallback, result:", tostring(ret) )
+					end
+					if interface.eventreadtimeout then
+						interface.eventreadtimeout:close( )
+						interface.eventreadtimeout = nil
+					end
+				end
+				local buffer, err, part = interface.conn:receive( pattern )  -- receive buffer with "pattern"
+				--vdebug( "read data:", tostring(buffer), "error:", tostring(err), "part:", tostring(part) )        
+				buffer = buffer or part or ""
+				local len = string_len( buffer )
+				if len > cfg.MAX_READ_LENGTH then  -- check buffer length
+					interface.fatalerror = "receive buffer exceeded"
+					debug( "fatal error:", interface.fatalerror )
+					interface:_close()
+					interface.eventread = nil
+					return -1
+				end
+				if err and ( err ~= "timeout" and err ~= "wantread" ) then
+					if "wantwrite" == err then -- need to read on write event
+						if not interface.eventwrite then  -- register new write event if needed
+							interface.eventwrite = addevent( base, interface.conn, EV_WRITE, interface.writecallback, cfg.WRITE_TIMEOUT )
+						end
+						interface.eventreadtimeout = addevent( base, nil, EV_TIMEOUT,
+							function( )
+								interface:_close()
+							end, cfg.READ_TIMEOUT
+						)             
+						debug( "wantwrite during read attemp, reg it in writecallback but dont know what really happens next..." )
+						-- to be honest i dont know what happens next, if it is allowed to first read, the write etc...
+					else  -- connection was closed or fatal error            
+						interface.fatalerror = err
+						debug( "connection failed in read event:", interface.fatalerror ) 
+						interface:_close()
+						interface.eventread = nil
+						return -1
+					end
+				end
+				interface.onincoming( interface, buffer, err )  -- send new data to listener
+				return EV_READ, cfg.READ_TIMEOUT
+			end
+		end
+
+		client:settimeout( 0 )  -- set non blocking
+		setmetatable(interface, interface_mt)
+		interfacelist( "add", interface )  -- add to interfacelist
+		return interface
+	end
+end
+
+local handleserver
+do
+	function handleserver( server, addr, port, pattern, listener, sslctx, startssl )  -- creates an server interface
+		debug "creating server interface..."
+		local interface = {
+			_connections = 0;
+			
+			conn = server;
+			onconnect = listener.onconnect;  -- will be called when new client connected
+			eventread = false;  -- read event handler
+			eventclose = false; -- close event handler
+			readcallback = false; -- read event callback
+			fatalerror = false; -- error message
+			nointerface = true;  -- lock/unlock parameter
+		}
+		interface.id = tostring(interface):match("%x+$");
+		interface.readcallback = function( event )  -- server handler, called on incoming connections
+			--vdebug( "server can accept, id/addr/port:", interface, addr, port )
+			if interface.fatalerror then
+				--vdebug( "leaving this event because:", self.fatalerror )
+				interface.eventread = nil
+				return -1
+			end
+			local delay = cfg.ACCEPT_DELAY
+			if EV_TIMEOUT == event then
+				if interface._connections >= cfg.MAX_CONNECTIONS then  -- check connection count
+					debug( "to many connections, seconds to wait for next accept:", delay )
+					return EV_TIMEOUT, delay  -- timeout...
+				else
+					return EV_READ  -- accept again
+				end
+			end
+			--vdebug("max connection check ok, accepting...")
+			local client, err = server:accept()    -- try to accept; TODO: check err
+			while client do
+				if interface._connections >= cfg.MAX_CONNECTIONS then
+					client:close( )  -- refuse connection
+					debug( "maximal connections reached, refuse client connection; accept delay:", delay )
+					return EV_TIMEOUT, delay  -- delay for next accept attemp
+				end
+				local ip, port = client:getpeername( )
+				interface._connections = interface._connections + 1  -- increase connection count
+				local clientinterface = handleclient( client, ip, port, interface, pattern, listener, nil, sslctx )
+				--vdebug( "client id:", clientinterface, "startssl:", startssl )
+				if startssl then
+					clientinterface:_start_ssl( clientinterface.onconnect )
+				else
+					clientinterface:_start_session( clientinterface.onconnect )
+				end
+				debug( "accepted incoming client connection from:", ip, port )
+				client, err = server:accept()    -- try to accept again
+			end
+			return EV_READ
+		end
+		
+		server:settimeout( 0 )
+		setmetatable(interface, interface_mt)
+		interfacelist( "add", interface )
+		interface:_start_session()
+		return interface
+	end
+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")
+		local server, err = socket.bind( addr, port, cfg.ACCEPT_QUEUE )  -- create server socket
+		if not server then
+			debug( "creating server socket failed because:", 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 = ssl.newcontext( 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
+	end
+end )( )
+
+local addclient, wrapclient
+do
+	function wrapclient( client, ip, port, listeners, pattern, sslctx, startssl )
+		local interface = handleclient( client, ip, port, nil, pattern, listeners, sslctx )
+		interface:_start_session()
+		return interface
+		--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
+		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 = ssl.newcontext( 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 server = function( )
+				return nil, "this is a dummy server interface"
+			end
+			local interface = wrapclient( client, ip, serverport, listeners, pattern, sslctx, startssl )
+			interface:_start_connection( startssl )
+			debug( "new connection id:", interface.id )
+			return interface, err
+		else
+			debug( "new connection failed:", err )
+			return nil, err
+		end
+	end
+end
+
+
+local loop = function( )  -- starts the event loop
+	return base:loop( )
+end
+
+local newevent = ( function( )
+	local add = base.addevent
+	return function( ... )
+		return add( base, ... )
+	end
+end )( )
+
+local closeallservers = function( arg )
+	for _, item in ipairs( interfacelist( ) ) do
+		if item "type" == "server" then
+			item( "close", arg )
+		end
+	end
+end
+
+return {
+
+	cfg = cfg,
+	base = base,
+	loop = loop,
+	event = event,
+	event_base = base,
+	addevent = newevent,
+	addserver = addserver,
+	addclient = addclient,
+	wrapclient = wrapclient,
+	closeallservers = closeallservers,
+
+	__NAME = SCRIPT_NAME,
+	__DATE = LAST_MODIFIED,
+	__AUTHOR = SCRIPT_AUTHOR,
+	__VERSION = SCRIPT_VERSION,
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/net/server_select.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -0,0 +1,914 @@
+-- 
+-- server.lua by blastbeat of the luadch project
+-- Re-used here under the MIT/X Consortium License
+-- 
+-- Modifications (C) 2008-2009 Matthew Wild, Waqas Hussain
+--
+
+-- // wrapping luadch stuff // --
+
+local use = function( what )
+    return _G[ what ]
+end
+local clean = function( tbl )
+    for i, k in pairs( tbl ) do
+        tbl[ i ] = nil
+    end
+end
+
+local log, table_concat = require ("util.logger").init("socket"), table.concat;
+local out_put = function (...) return log("debug", table_concat{...}); end
+local out_error = function (...) return log("warn", table_concat{...}); end
+local mem_free = collectgarbage
+
+----------------------------------// DECLARATION //--
+
+--// constants //--
+
+local STAT_UNIT = 1    -- byte
+
+--// lua functions //--
+
+local type = use "type"
+local pairs = use "pairs"
+local ipairs = use "ipairs"
+local tostring = use "tostring"
+local collectgarbage = use "collectgarbage"
+
+--// lua libs //--
+
+local os = use "os"
+local table = use "table"
+local string = use "string"
+local coroutine = use "coroutine"
+
+--// lua lib methods //--
+
+local os_time = os.time
+local os_difftime = os.difftime
+local table_concat = table.concat
+local table_remove = table.remove
+local string_len = string.len
+local string_sub = string.sub
+local coroutine_wrap = coroutine.wrap
+local coroutine_yield = coroutine.yield
+
+--// extern libs //--
+
+local luasec = select( 2, pcall( require, "ssl" ) )
+local luasocket = require "socket"
+
+--// extern lib methods //--
+
+local ssl_wrap = ( luasec and luasec.wrap )
+local socket_bind = luasocket.bind
+local socket_sleep = luasocket.sleep
+local socket_select = luasocket.select
+local ssl_newcontext = ( luasec and luasec.newcontext )
+
+--// functions //--
+
+local id
+local loop
+local stats
+local idfalse
+local addtimer
+local closeall
+local addserver
+local getserver
+local wrapserver
+local getsettings
+local closesocket
+local removesocket
+local removeserver
+local changetimeout
+local wrapconnection
+local changesettings
+
+--// tables //--
+
+local _server
+local _readlist
+local _timerlist
+local _sendlist
+local _socketlist
+local _closelist
+local _readtimes
+local _writetimes
+
+--// simple data types //--
+
+local _
+local _readlistlen
+local _sendlistlen
+local _timerlistlen
+
+local _sendtraffic
+local _readtraffic
+
+local _selecttimeout
+local _sleeptime
+
+local _starttime
+local _currenttime
+
+local _maxsendlen
+local _maxreadlen
+
+local _checkinterval
+local _sendtimeout
+local _readtimeout
+
+local _cleanqueue
+
+local _timer
+
+local _maxclientsperserver
+
+----------------------------------// DEFINITION //--
+
+_server = { }    -- key = port, value = table; list of listening servers
+_readlist = { }    -- array with sockets to read from
+_sendlist = { }    -- arrary with sockets to write to
+_timerlist = { }    -- array of timer functions
+_socketlist = { }    -- key = socket, value = wrapped socket (handlers)
+_readtimes = { }   -- key = handler, value = timestamp of last data reading
+_writetimes = { }   -- key = handler, value = timestamp of last data writing/sending
+_closelist = { }    -- handlers to close
+
+_readlistlen = 0    -- length of readlist
+_sendlistlen = 0    -- length of sendlist
+_timerlistlen = 0    -- lenght of timerlist
+
+_sendtraffic = 0    -- some stats
+_readtraffic = 0
+
+_selecttimeout = 1    -- timeout of socket.select
+_sleeptime = 0    -- time to wait at the end of every loop
+
+_maxsendlen = 51000 * 1024    -- max len of send buffer
+_maxreadlen = 25000 * 1024    -- max len of read buffer
+
+_checkinterval = 1200000    -- interval in secs to check idle clients
+_sendtimeout = 60000    -- allowed send idle time in secs
+_readtimeout = 6 * 60 * 60    -- allowed read idle time in secs
+
+_cleanqueue = false    -- clean bufferqueue after using
+
+_maxclientsperserver = 1000
+
+_maxsslhandshake = 30 -- max handshake round-trips
+----------------------------------// PRIVATE //--
+
+wrapserver = function( listeners, socket, ip, serverport, pattern, sslctx, maxconnections, startssl )    -- this function wraps a server
+
+    maxconnections = maxconnections or _maxclientsperserver
+
+    local connections = 0
+
+    local dispatch, disconnect = listeners.onincoming, listeners.ondisconnect
+
+    local err
+
+    local ssl = false
+
+    if sslctx then
+        ssl = true
+        if not ssl_newcontext then
+            out_error "luasec not found"
+            ssl = false
+        end
+        if type( sslctx ) ~= "table" then
+            out_error "server.lua: wrong server sslctx"
+            ssl = false
+        end
+        local ctx;
+        ctx, err = ssl_newcontext( sslctx )
+        if not ctx then
+            err = err or "wrong sslctx parameters"
+            local file;
+            file = err:match("^error loading (.-) %(");
+            if file then
+            	if file == "private key" then
+            		file = sslctx.key or "your private key";
+            	elseif file == "certificate" then
+            		file = sslctx.certificate or "your certificate file";
+            	end
+	        local reason = err:match("%((.+)%)$") or "some reason";
+	        if reason == "Permission denied" then
+	        	reason = "Check that the permissions allow Prosody to read this file.";
+	        elseif reason == "No such file or directory" then
+	        	reason = "Check that the path is correct, and the file exists.";
+	        elseif reason == "system lib" then
+	        	reason = "Previous error (see logs), or other system error.";
+	        else
+	        	reason = "Reason: "..tostring(reason or "unknown"):lower();
+	        end
+	        log("error", "SSL/TLS: Failed to load %s: %s", file, reason);
+	    else
+                log("error", "SSL/TLS: Error initialising for port %d: %s", serverport, err );
+            end
+            ssl = false
+        end
+        sslctx = ctx;
+    end
+    if not ssl then
+      sslctx = false;
+      if startssl then
+         log("error", "Failed to listen on port %d due to SSL/TLS to SSL/TLS initialisation errors (see logs)", serverport )
+         return nil, "Cannot start ssl,  see log for details"
+       end
+    end
+
+    local accept = socket.accept
+
+    --// public methods of the object //--
+
+    local handler = { }
+
+    handler.shutdown = function( ) end
+
+    handler.ssl = function( )
+        return ssl
+    end
+    handler.sslctx = function( )
+        return sslctx
+    end
+    handler.remove = function( )
+        connections = connections - 1
+    end
+    handler.close = function( )
+        for _, handler in pairs( _socketlist ) do
+            if handler.serverport == serverport then
+                handler.disconnect( handler, "server closed" )
+                handler:close( true )
+            end
+        end
+        socket:close( )
+        _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )
+        _readlistlen = removesocket( _readlist, socket, _readlistlen )
+        _socketlist[ socket ] = nil
+        handler = nil
+        socket = nil
+        --mem_free( )
+        out_put "server.lua: closed server handler and removed sockets from list"
+    end
+    handler.ip = function( )
+        return ip
+    end
+    handler.serverport = function( )
+        return serverport
+    end
+    handler.socket = function( )
+        return socket
+    end
+    handler.readbuffer = function( )
+        if connections > maxconnections then
+            out_put( "server.lua: refused new client connection: server full" )
+            return false
+        end
+        local client, err = accept( socket )    -- try to accept
+        if client then
+            local ip, clientport = client:getpeername( )
+            client:settimeout( 0 )
+            local handler, client, err = wrapconnection( handler, listeners, client, ip, serverport, clientport, pattern, sslctx, startssl )    -- wrap new client socket
+            if err then    -- error while wrapping ssl socket
+                return false
+            end
+            connections = connections + 1
+            out_put( "server.lua: accepted new client connection from ", tostring(ip), ":", tostring(clientport), " to ", tostring(serverport))
+            return dispatch( handler )
+        elseif err then    -- maybe timeout or something else
+            out_put( "server.lua: error with new client connection: ", tostring(err) )
+            return false
+        end
+    end
+    return handler
+end
+
+wrapconnection = function( server, listeners, socket, ip, serverport, clientport, pattern, sslctx, startssl )    -- this function wraps a client to a handler object
+
+    socket:settimeout( 0 )
+
+    --// local import of socket methods //--
+
+    local send
+    local receive
+    local shutdown
+
+    --// private closures of the object //--
+
+    local ssl
+
+    local dispatch = listeners.onincoming
+    local status = listeners.status
+    local disconnect = listeners.ondisconnect
+
+    local bufferqueue = { }    -- buffer array
+    local bufferqueuelen = 0    -- end of buffer array
+
+    local toclose
+    local fatalerror
+    local needtls
+
+    local bufferlen = 0
+
+    local noread = false
+    local nosend = false
+
+    local sendtraffic, readtraffic = 0, 0
+
+    local maxsendlen = _maxsendlen
+    local maxreadlen = _maxreadlen
+
+    --// public methods of the object //--
+
+    local handler = bufferqueue    -- saves a table ^_^
+
+    handler.dispatch = function( )
+        return dispatch
+    end
+    handler.disconnect = function( )
+        return disconnect
+    end
+    handler.setlistener = function( self, listeners )
+        dispatch = listeners.onincoming
+        disconnect = listeners.ondisconnect
+    end
+    handler.getstats = function( )
+        return readtraffic, sendtraffic
+    end
+    handler.ssl = function( )
+        return ssl
+    end
+    handler.sslctx = function ( )
+        return sslctx
+    end
+    handler.send = function( _, data, i, j )
+        return send( socket, data, i, j )
+    end
+    handler.receive = function( pattern, prefix )
+        return receive( socket, pattern, prefix )
+    end
+    handler.shutdown = function( pattern )
+        return shutdown( socket, pattern )
+    end
+    handler.close = function( self, forced )
+        if not handler then return true; end
+        _readlistlen = removesocket( _readlist, socket, _readlistlen )
+        _readtimes[ handler ] = nil
+        if bufferqueuelen ~= 0 then
+            if not ( forced or fatalerror ) then
+                handler.sendbuffer( )
+                if bufferqueuelen ~= 0 then   -- try again...
+                    if handler then
+                        handler.write = nil    -- ... but no further writing allowed
+                    end
+                    toclose = true
+                    return false
+                end
+            else
+                send( socket, table_concat( bufferqueue, "", 1, bufferqueuelen ), 1, bufferlen )    -- forced send
+            end
+        end
+        if socket then
+          _ = shutdown and shutdown( socket )
+          socket:close( )
+          _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )
+          _socketlist[ socket ] = nil
+          socket = nil
+        else
+          out_put "server.lua: socket already closed"
+        end
+        if handler then
+            _writetimes[ handler ] = nil
+            _closelist[ handler ] = nil
+            handler = nil
+        end
+	if server then
+		server.remove( )
+	end
+        out_put "server.lua: closed client handler and removed socket from list"
+        return true
+    end
+    handler.ip = function( )
+        return ip
+    end
+    handler.serverport = function( )
+        return serverport
+    end
+    handler.clientport = function( )
+        return clientport
+    end
+    local write = function( self, data )
+        bufferlen = bufferlen + string_len( data )
+        if bufferlen > maxsendlen then
+            _closelist[ handler ] = "send buffer exceeded"   -- cannot close the client at the moment, have to wait to the end of the cycle
+            handler.write = idfalse    -- dont write anymore
+            return false
+        elseif socket and not _sendlist[ socket ] then
+            _sendlistlen = addsocket(_sendlist, socket, _sendlistlen)
+        end
+        bufferqueuelen = bufferqueuelen + 1
+        bufferqueue[ bufferqueuelen ] = data
+        if handler then
+        	_writetimes[ handler ] = _writetimes[ handler ] or _currenttime
+        end
+        return true
+    end
+    handler.write = write
+    handler.bufferqueue = function( self )
+        return bufferqueue
+    end
+    handler.socket = function( self )
+        return socket
+    end
+    handler.pattern = function( self, new )
+        pattern = new or pattern
+        return pattern
+    end
+    handler.set_send = function ( self, newsend )
+        send = newsend or send
+        return send
+    end
+    handler.bufferlen = function( self, readlen, sendlen )
+        maxsendlen = sendlen or maxsendlen
+        maxreadlen = readlen or maxreadlen
+        return maxreadlen, maxsendlen
+    end
+    handler.lock = function( self, switch )
+        if switch == true then
+            handler.write = idfalse
+            local tmp = _sendlistlen
+            _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )
+            _writetimes[ handler ] = nil
+            if _sendlistlen ~= tmp then
+                nosend = true
+            end
+            tmp = _readlistlen
+            _readlistlen = removesocket( _readlist, socket, _readlistlen )
+            _readtimes[ handler ] = nil
+            if _readlistlen ~= tmp then
+                noread = true
+            end
+        elseif switch == false then
+            handler.write = write
+            if noread then
+                noread = false
+                _readlistlen = addsocket(_readlist, socket, _readlistlen)
+                _readtimes[ handler ] = _currenttime
+            end
+            if nosend then
+                nosend = false
+                write( "" )
+            end
+        end
+        return noread, nosend
+    end
+    local _readbuffer = function( )    -- this function reads data
+        local buffer, err, part = receive( socket, pattern )    -- receive buffer with "pattern"
+        if not err or ( err == "timeout" or err == "wantread" ) then    -- received something
+            local buffer = buffer or part or ""
+            local len = string_len( buffer )
+            if len > maxreadlen then
+                disconnect( handler, "receive buffer exceeded" )
+                handler.close( true )
+                return false
+            end
+            local count = len * STAT_UNIT
+            readtraffic = readtraffic + count
+            _readtraffic = _readtraffic + count
+            _readtimes[ handler ] = _currenttime
+            --out_put( "server.lua: read data '", buffer:gsub("[^%w%p ]", "."), "', error: ", err )
+            return dispatch( handler, buffer, err )
+        else    -- connections was closed or fatal error
+            out_put( "server.lua: client ", tostring(ip), ":", tostring(clientport), " read error: ", tostring(err) )
+            fatalerror = true
+            disconnect( handler, err )
+	    _ = handler and handler.close( )
+            return false
+        end
+    end
+    local _sendbuffer = function( )    -- this function sends data
+    	local succ, err, byte, buffer, count;
+    	local count;
+    	if socket then
+            buffer = table_concat( bufferqueue, "", 1, bufferqueuelen )
+            succ, err, byte = send( socket, buffer, 1, bufferlen )
+            count = ( succ or byte or 0 ) * STAT_UNIT
+            sendtraffic = sendtraffic + count
+            _sendtraffic = _sendtraffic + count
+            _ = _cleanqueue and clean( bufferqueue )
+            --out_put( "server.lua: sended '", buffer, "', bytes: ", tostring(succ), ", error: ", tostring(err), ", part: ", tostring(byte), ", to: ", tostring(ip), ":", tostring(clientport) )
+        else
+            succ, err, count = false, "closed", 0;
+        end
+        if succ then    -- sending succesful
+            bufferqueuelen = 0
+            bufferlen = 0
+            _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )    -- delete socket from writelist
+            _ = needtls and handler:starttls(true)
+            _writetimes[ handler ] = nil
+	    _ = toclose and handler.close( )
+            return true
+        elseif byte and ( err == "timeout" or err == "wantwrite" ) then    -- want write
+            buffer = string_sub( buffer, byte + 1, bufferlen )    -- new buffer
+            bufferqueue[ 1 ] = buffer    -- insert new buffer in queue
+            bufferqueuelen = 1
+            bufferlen = bufferlen - byte
+            _writetimes[ handler ] = _currenttime
+            return true
+        else    -- connection was closed during sending or fatal error
+            out_put( "server.lua: client ", tostring(ip), ":", tostring(clientport), " write error: ", tostring(err) )
+            fatalerror = true
+            disconnect( handler, err )
+            _ = handler and handler.close( )
+            return false
+        end
+    end
+
+    -- Set the sslctx
+    local handshake;
+    function handler.set_sslctx(self, new_sslctx)
+        ssl = true
+        sslctx = new_sslctx;
+        local wrote
+        local read
+        handshake = coroutine_wrap( function( client )    -- create handshake coroutine
+                local err
+                for i = 1, _maxsslhandshake do
+                    _sendlistlen = ( wrote and removesocket( _sendlist, client, _sendlistlen ) ) or _sendlistlen
+                    _readlistlen = ( read and removesocket( _readlist, client, _readlistlen ) ) or _readlistlen
+                    read, wrote = nil, nil
+                    _, err = client:dohandshake( )
+                    if not err then
+                        out_put( "server.lua: ssl handshake done" )
+                        handler.readbuffer = _readbuffer    -- when handshake is done, replace the handshake function with regular functions
+                        handler.sendbuffer = _sendbuffer
+                        _ = status and status( handler, "ssl-handshake-complete" )
+                        _readlistlen = addsocket(_readlist, client, _readlistlen)
+                        return true
+                    else
+                       out_put( "server.lua: error during ssl handshake: ", tostring(err) )
+                       if err == "wantwrite" and not wrote then
+                           _sendlistlen = addsocket(_sendlist, client, _sendlistlen)
+                           wrote = true
+                       elseif err == "wantread" and not read then
+                           _readlistlen = addsocket(_readlist, client, _readlistlen)
+                           read = true
+                       else
+                           break;
+                       end
+                       --coroutine_yield( handler, nil, err )    -- handshake not finished
+                       coroutine_yield( )
+                    end
+                end
+                disconnect( handler, "ssl handshake failed" )
+                _ = handler and handler:close( true )    -- forced disconnect
+                return false    -- handshake failed
+            end
+        )
+    end
+    if sslctx then    -- ssl?
+    	handler:set_sslctx(sslctx);
+        if startssl then    -- ssl now?
+            --out_put("server.lua: ", "starting ssl handshake")
+	    local err
+            socket, err = ssl_wrap( socket, sslctx )    -- wrap socket
+            if err then
+                out_put( "server.lua: ssl error: ", tostring(err) )
+                --mem_free( )
+                return nil, nil, err    -- fatal error
+            end
+            socket:settimeout( 0 )
+            handler.readbuffer = handshake
+            handler.sendbuffer = handshake
+            handshake( socket ) -- do handshake
+            if not socket then
+                return nil, nil, "ssl handshake failed";
+            end
+        else
+            -- We're not automatically doing SSL, so we're not secure (yet)
+            ssl = false
+            handler.starttls = function( self, now )
+                if not now then
+                    --out_put "server.lua: we need to do tls, but delaying until later"
+                    needtls = true
+                    return
+                end
+                --out_put( "server.lua: attempting to start tls on " .. tostring( socket ) )
+                local oldsocket, err = socket
+                socket, err = ssl_wrap( socket, sslctx )    -- wrap socket
+                --out_put( "server.lua: sslwrapped socket is " .. tostring( socket ) )
+                if err then
+                    out_put( "server.lua: error while starting tls on client: ", tostring(err) )
+                    return nil, err    -- fatal error
+                end
+
+                socket:settimeout( 0 )
+
+                -- add the new socket to our system
+
+                send = socket.send
+                receive = socket.receive
+                shutdown = id
+
+                _socketlist[ socket ] = handler
+                _readlistlen = addsocket(_readlist, socket, _readlistlen)
+
+                -- remove traces of the old socket
+
+                _readlistlen = removesocket( _readlist, oldsocket, _readlistlen )
+                _sendlistlen = removesocket( _sendlist, oldsocket, _sendlistlen )
+                _socketlist[ oldsocket ] = nil
+
+                handler.starttls = nil
+                needtls = nil
+                
+                -- Secure now
+                ssl = true
+
+                handler.readbuffer = handshake
+                handler.sendbuffer = handshake
+                handshake( socket )    -- do handshake
+            end
+            handler.readbuffer = _readbuffer
+            handler.sendbuffer = _sendbuffer
+        end
+    else    -- normal connection
+        ssl = false
+        handler.readbuffer = _readbuffer
+        handler.sendbuffer = _sendbuffer
+    end
+
+    send = socket.send
+    receive = socket.receive
+    shutdown = ( ssl and id ) or socket.shutdown
+
+    _socketlist[ socket ] = handler
+    _readlistlen = addsocket(_readlist, socket, _readlistlen)
+
+    return handler, socket
+end
+
+id = function( )
+end
+
+idfalse = function( )
+    return false
+end
+
+addsocket = function( list, socket, len )
+    if not list[ socket ] then
+      len = len + 1
+      list[ len ] = socket
+      list[ socket ] = len
+    end
+    return len;
+end
+
+removesocket = function( list, socket, len )    -- this function removes sockets from a list ( copied from copas )
+    local pos = list[ socket ]
+    if pos then
+        list[ socket ] = nil
+        local last = list[ len ]
+        list[ len ] = nil
+        if last ~= socket then
+            list[ last ] = pos
+            list[ pos ] = last
+        end
+        return len - 1
+    end
+    return len
+end
+
+closesocket = function( socket )
+    _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )
+    _readlistlen = removesocket( _readlist, socket, _readlistlen )
+    _socketlist[ socket ] = nil
+    socket:close( )
+    --mem_free( )
+end
+
+----------------------------------// PUBLIC //--
+
+addserver = function( addr, port, listeners, pattern, sslctx, startssl )    -- this function provides a way for other scripts to reg a server
+    local err
+    --out_put("server.lua: autossl on ", port, " is ", startssl)
+    if type( listeners ) ~= "table" then
+        err = "invalid listener table"
+    end
+    if not type( port ) == "number" or not ( port >= 0 and port <= 65535 ) then
+        err = "invalid port"
+    elseif _server[ port ] then
+        err =  "listeners on port '" .. port .. "' already exist"
+    elseif sslctx and not luasec then
+        err = "luasec not found"
+    end
+    if err then
+        out_error( "server.lua, port ", port, ": ", err )
+        return nil, err
+    end
+    addr = addr or "*"
+    local server, err = socket_bind( addr, port )
+    if err then
+        out_error( "server.lua, port ", port, ": ", err )
+        return nil, err
+    end
+    local handler, err = wrapserver( listeners, server, addr, port, pattern, sslctx, _maxclientsperserver, startssl )    -- wrap new server socket
+    if not handler then
+        server:close( )
+        return nil, err
+    end
+    server:settimeout( 0 )
+    _readlistlen = addsocket(_readlist, server, _readlistlen)
+    _server[ port ] = handler
+    _socketlist[ server ] = handler
+    out_put( "server.lua: new server listener on '", addr, ":", port, "'" )
+    return handler
+end
+
+getserver = function ( port )
+	return _server[ port ];
+end
+
+removeserver = function( port )
+    local handler = _server[ port ]
+    if not handler then
+        return nil, "no server found on port '" .. tostring( port ) .. "'"
+    end
+    handler:close( )
+    _server[ port ] = nil
+    return true
+end
+
+closeall = function( )
+    for _, handler in pairs( _socketlist ) do
+        handler:close( )
+        _socketlist[ _ ] = nil
+    end
+    _readlistlen = 0
+    _sendlistlen = 0
+    _timerlistlen = 0
+    _server = { }
+    _readlist = { }
+    _sendlist = { }
+    _timerlist = { }
+    _socketlist = { }
+    --mem_free( )
+end
+
+getsettings = function( )
+    return  _selecttimeout, _sleeptime, _maxsendlen, _maxreadlen, _checkinterval, _sendtimeout, _readtimeout, _cleanqueue, _maxclientsperserver, _maxsslhandshake
+end
+
+changesettings = function( new )
+    if type( new ) ~= "table" then
+        return nil, "invalid settings table"
+    end
+    _selecttimeout = tonumber( new.timeout ) or _selecttimeout
+    _sleeptime = tonumber( new.sleeptime ) or _sleeptime
+    _maxsendlen = tonumber( new.maxsendlen ) or _maxsendlen
+    _maxreadlen = tonumber( new.maxreadlen ) or _maxreadlen
+    _checkinterval = tonumber( new.checkinterval ) or _checkinterval
+    _sendtimeout = tonumber( new.sendtimeout ) or _sendtimeout
+    _readtimeout = tonumber( new.readtimeout ) or _readtimeout
+    _cleanqueue = new.cleanqueue
+    _maxclientsperserver = new._maxclientsperserver or _maxclientsperserver
+    _maxsslhandshake = new._maxsslhandshake or _maxsslhandshake
+    return true
+end
+
+addtimer = function( listener )
+    if type( listener ) ~= "function" then
+        return nil, "invalid listener function"
+    end
+    _timerlistlen = _timerlistlen + 1
+    _timerlist[ _timerlistlen ] = listener
+    return true
+end
+
+stats = function( )
+    return _readtraffic, _sendtraffic, _readlistlen, _sendlistlen, _timerlistlen
+end
+
+local dontstop = true; -- thinking about tomorrow, ...
+
+setquitting = function (quit)
+	dontstop = not quit;
+	return;
+end
+
+loop = function( )    -- this is the main loop of the program
+    while dontstop do
+        local read, write, err = socket_select( _readlist, _sendlist, _selecttimeout )
+        for i, socket in ipairs( write ) do    -- send data waiting in writequeues
+            local handler = _socketlist[ socket ]
+            if handler then
+                handler.sendbuffer( )
+            else
+                closesocket( socket )
+                out_put "server.lua: found no handler and closed socket (writelist)"    -- this should not happen
+            end
+        end
+        for i, socket in ipairs( read ) do    -- receive data
+            local handler = _socketlist[ socket ]
+            if handler then
+                handler.readbuffer( )
+            else
+                closesocket( socket )
+                out_put "server.lua: found no handler and closed socket (readlist)"    -- this can happen
+            end
+        end
+        for handler, err in pairs( _closelist ) do
+            handler.disconnect( )( handler, err )
+            handler:close( true )    -- forced disconnect
+        end
+        clean( _closelist )
+        _currenttime = os_time( )
+        if os_difftime( _currenttime - _timer ) >= 1 then
+            for i = 1, _timerlistlen do
+                _timerlist[ i ]( _currenttime )    -- fire timers
+            end
+            _timer = _currenttime
+        end
+        socket_sleep( _sleeptime )    -- wait some time
+        --collectgarbage( )
+    end
+    return "quitting"
+end
+
+--// EXPERIMENTAL //--
+
+local wrapclient = function( socket, ip, serverport, listeners, pattern, sslctx, startssl )
+    local handler = wrapconnection( nil, listeners, socket, ip, serverport, "clientport", pattern, sslctx, startssl )
+    _socketlist[ socket ] = handler
+    _sendlistlen = addsocket(_sendlist, socket, _sendlistlen)
+    return handler, socket
+end
+
+local addclient = function( address, port, listeners, pattern, sslctx, startssl )
+    local client, err = luasocket.tcp( )
+    if err then
+        return nil, err
+    end
+    client:settimeout( 0 )
+    _, err = client:connect( address, port )
+    if err then    -- try again
+        local handler = wrapclient( client, address, port, listeners )
+    else
+        wrapconnection( nil, listeners, client, address, port, "clientport", pattern, sslctx, startssl )
+    end
+end
+
+--// EXPERIMENTAL //--
+
+----------------------------------// BEGIN //--
+
+use "setmetatable" ( _socketlist, { __mode = "k" } )
+use "setmetatable" ( _readtimes, { __mode = "k" } )
+use "setmetatable" ( _writetimes, { __mode = "k" } )
+
+_timer = os_time( )
+_starttime = os_time( )
+
+addtimer( function( )
+        local difftime = os_difftime( _currenttime - _starttime )
+        if difftime > _checkinterval then
+            _starttime = _currenttime
+            for handler, timestamp in pairs( _writetimes ) do
+                if os_difftime( _currenttime - timestamp ) > _sendtimeout then
+                    --_writetimes[ handler ] = nil
+                    handler.disconnect( )( handler, "send timeout" )
+                    handler:close( true )    -- forced disconnect
+                end
+            end
+            for handler, timestamp in pairs( _readtimes ) do
+                if os_difftime( _currenttime - timestamp ) > _readtimeout then
+                    --_readtimes[ handler ] = nil
+                    handler.disconnect( )( handler, "read timeout" )
+                    handler:close( )    -- forced disconnect?
+                end
+            end
+        end
+    end
+)
+
+----------------------------------// PUBLIC INTERFACE //--
+
+return {
+
+    addclient = addclient,
+    wrapclient = wrapclient,
+    
+    loop = loop,
+    stats = stats,
+    closeall = closeall,
+    addtimer = addtimer,
+    addserver = addserver,
+    getserver = getserver,
+    getsettings = getsettings,
+    setquitting = setquitting,
+    removeserver = removeserver,
+    changesettings = changesettings,
+}
--- a/net/xmppclient_listener.lua	Sun Nov 29 21:32:39 2009 +0100
+++ b/net/xmppclient_listener.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -61,7 +61,7 @@
 		function session.data(conn, data)
 			local ok, err = parser:parse(data);
 			if ok then return; end
-			log("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "));
+			log("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "):gsub("[%z\1-\31]", "_"));
 			session:close("xml-not-well-formed");
 		end
 		
@@ -100,15 +100,15 @@
 			end
 		end
 		session.send("</stream:stream>");
-		session.conn.close();
-		xmppclient.disconnect(session.conn, (reason and (reason.text or reason.condition)) or reason or "session closed");
+		session.conn:close();
+		xmppclient.ondisconnect(session.conn, (reason and (reason.text or reason.condition)) or reason or "session closed");
 	end
 end
 
 
 -- End of session methods --
 
-function xmppclient.listener(conn, data)
+function xmppclient.onincoming(conn, data)
 	local session = sessions[conn];
 	if not session then
 		session = sm_new_session(conn);
@@ -117,7 +117,7 @@
 		session.log("info", "Client connected");
 		
 		-- Client is using legacy SSL (otherwise mod_tls sets this flag)
-		if conn.ssl() then
+		if conn:ssl() then
 			session.secure = true;
 		end
 		
@@ -133,14 +133,13 @@
 	end
 end
 	
-function xmppclient.disconnect(conn, err)
+function xmppclient.ondisconnect(conn, err)
 	local session = sessions[conn];
 	if session then
 		(session.log or log)("info", "Client disconnected: %s", err);
 		sm_destroy_session(session, err);
 		sessions[conn]  = nil;
 		session = nil;
-		collectgarbage("collect");
 	end
 end
 
--- a/net/xmppcomponent_listener.lua	Sun Nov 29 21:32:39 2009 +0100
+++ b/net/xmppcomponent_listener.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -118,16 +118,16 @@
 		end
 		session.send("</stream:stream>");
 		session.conn.close();
-		component_listener.disconnect(session.conn, "stream error");
+		component_listener.ondisconnect(session.conn, "stream error");
 	end
 end
 
 --- Component connlistener
-function component_listener.listener(conn, data)
+function component_listener.onincoming(conn, data)
 	local session = sessions[conn];
 	if not session then
 		local _send = conn.write;
-		session = { type = "component", conn = conn, send = function (data) return _send(tostring(data)); end };
+		session = { type = "component", conn = conn, send = function (data) return _send(conn, tostring(data)); end };
 		sessions[conn] = session;
 
 		-- Logging functions --
@@ -157,7 +157,7 @@
 	end
 end
 	
-function component_listener.disconnect(conn, err)
+function component_listener.ondisconnect(conn, err)
 	local session = sessions[conn];
 	if session then
 		(session.log or log)("info", "component disconnected: %s (%s)", tostring(session.host), tostring(err));
@@ -169,7 +169,6 @@
 		sessions[conn]  = nil;
 		for k in pairs(session) do session[k] = nil; end
 		session = nil;
-		collectgarbage("collect");
 	end
 end
 
--- a/net/xmppserver_listener.lua	Sun Nov 29 21:32:39 2009 +0100
+++ b/net/xmppserver_listener.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -100,18 +100,18 @@
 			end
 		end
 		session.sends2s("</stream:stream>");
-		if session.notopen or not session.conn.close() then
-			session.conn.close(true); -- Force FIXME: timer?
+		if session.notopen or not session.conn:close() then
+			session.conn:close(true); -- Force FIXME: timer?
 		end
-		session.conn.close();
-		xmppserver.disconnect(session.conn, "stream error");
+		session.conn:close();
+		xmppserver.ondisconnect(session.conn, "stream error");
 	end
 end
 
 
 -- End of session methods --
 
-function xmppserver.listener(conn, data)
+function xmppserver.onincoming(conn, data)
 	local session = sessions[conn];
 	if not session then
 		session = s2s_new_incoming(conn);
@@ -148,7 +148,7 @@
 	end
 end
 
-function xmppserver.disconnect(conn, err)
+function xmppserver.ondisconnect(conn, err)
 	local session = sessions[conn];
 	if session then
 		if err and err ~= "closed" and session.srv_hosts then
@@ -162,7 +162,6 @@
 		s2s_destroy_session(session);
 		sessions[conn]  = nil;
 		session = nil;
-		collectgarbage("collect");
 	end
 end
 
--- a/plugins/mod_bosh.lua	Sun Nov 29 21:32:39 2009 +0100
+++ b/plugins/mod_bosh.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -152,7 +152,7 @@
 		local r, send_buffer = session.requests, session.send_buffer;
 		local response = { headers = default_headers }
 		function session.send(s)
-			log("debug", "Sending BOSH data: %s", tostring(s));
+			--log("debug", "Sending BOSH data: %s", tostring(s));
 			local oldest_request = r[1];
 			while oldest_request and oldest_request.destroyed do
 				t_remove(r, 1);
@@ -160,7 +160,7 @@
 				oldest_request = r[1];
 			end
 			if oldest_request then
-				log("debug", "We have an open request, so using that to send with");
+				log("debug", "We have an open request, so sending on that");
 				response.body = t_concat{"<body xmlns='http://jabber.org/protocol/httpbind' sid='", sid, "' xmlns:stream = 'http://etherx.jabber.org/streams'>", tostring(s), "</body>" };
 				oldest_request:send(response);
 				--log("debug", "Sent");
--- a/plugins/mod_console.lua	Sun Nov 29 21:32:39 2009 +0100
+++ b/plugins/mod_console.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -33,11 +33,11 @@
 console = {};
 
 function console:new_session(conn)
-	local w = function(s) conn.write(s:gsub("\n", "\r\n")); end;
+	local w = function(s) conn:write(s:gsub("\n", "\r\n")); end;
 	local session = { conn = conn;
 			send = function (t) w(tostring(t)); end;
 			print = function (t) w("| "..tostring(t).."\n"); end;
-			disconnect = function () conn.close(); end;
+			disconnect = function () conn:close(); end;
 			};
 	session.env = setmetatable({}, default_env_mt);
 	
@@ -53,7 +53,7 @@
 
 local sessions = {};
 
-function console_listener.listener(conn, data)
+function console_listener.onincoming(conn, data)
 	local session = sessions[conn];
 	
 	if not session then
@@ -126,7 +126,7 @@
 	session.send(string.char(0));
 end
 
-function console_listener.disconnect(conn, err)
+function console_listener.ondisconnect(conn, err)
 	local session = sessions[conn];
 	if session then
 		session.disconnect();
--- a/plugins/mod_presence.lua	Sun Nov 29 21:32:39 2009 +0100
+++ b/plugins/mod_presence.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -200,9 +200,6 @@
 			rostermanager.roster_push(node, host, to_bare);
 		end
 		core_route_stanza(origin, stanza);
-		-- COMPAT: Some legacy clients keep displaying unsubscribed contacts as online unless an unavailable presence is sent:
-		send_presence_of_available_resources(node, host, to_bare, origin, core_route_stanza,
-			st.presence({ type="unavailable", from=from_bare, to=to_bare, id=stanza.attr.id }));
 	end
 	stanza.attr.from, stanza.attr.to = st_from, st_to;
 end
@@ -223,16 +220,17 @@
 				-- TODO send last recieved unavailable presence (or we MAY do nothing, which is fine too)
 			end
 		else
-			core_route_stanza(origin, st.presence({from=to_bare, to=from_bare, type="unsubscribed"}));
+			core_route_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unsubscribed"}));
 		end
 	elseif stanza.attr.type == "subscribe" then
 		if rostermanager.is_contact_subscribed(node, host, from_bare) then
-			core_route_stanza(origin, st.presence({from=to_bare, to=from_bare, type="subscribed"})); -- already subscribed
+			core_route_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="subscribed"})); -- already subscribed
 			-- Sending presence is not clearly stated in the RFC, but it seems appropriate
 			if 0 == send_presence_of_available_resources(node, host, from_bare, origin, core_route_stanza) then
 				-- TODO send last recieved unavailable presence (or we MAY do nothing, which is fine too)
 			end
 		else
+			core_route_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unavailable"})); -- acknowledging receipt
 			if not rostermanager.is_contact_pending_in(node, host, from_bare) then
 				if rostermanager.set_contact_pending_in(node, host, from_bare) then
 					sessionmanager.send_to_available_resources(node, host, stanza);
@@ -241,14 +239,17 @@
 		end
 	elseif stanza.attr.type == "unsubscribe" then
 		if rostermanager.process_inbound_unsubscribe(node, host, from_bare) then
+			sessionmanager.send_to_interested_resources(node, host, stanza);
 			rostermanager.roster_push(node, host, from_bare);
 		end
 	elseif stanza.attr.type == "subscribed" then
 		if rostermanager.process_inbound_subscription_approval(node, host, from_bare) then
+			sessionmanager.send_to_interested_resources(node, host, stanza);
 			rostermanager.roster_push(node, host, from_bare);
 		end
 	elseif stanza.attr.type == "unsubscribed" then
 		if rostermanager.process_inbound_subscription_cancellation(node, host, from_bare) then
+			sessionmanager.send_to_interested_resources(node, host, stanza);
 			rostermanager.roster_push(node, host, from_bare);
 		end
 	end -- discard any other type
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/mod_proxy65.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -0,0 +1,255 @@
+-- Copyright (C) 2009 Thilo Cestonaro
+-- 
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+--[[
+* to restart the proxy in the console: e.g.
+module:unload("proxy65");
+> server.removeserver(<proxy65_port>);
+module:load("proxy65", <proxy65_jid>);
+]]--
+
+if module:get_host_type() ~= "component" then
+	error("proxy65 should be loaded as a component, please see http://prosody.im/doc/components", 0);
+end
+
+local jid_split, jid_join = require "util.jid".split, require "util.jid".join;
+local st = require "util.stanza";
+local componentmanager = require "core.componentmanager";
+local config_get = require "core.configmanager".get;
+local connlisteners = require "net.connlisteners";
+local sha1 = require "util.hashes".sha1;
+
+local host, name = module:get_host(), "SOCKS5 Bytestreams Service";
+local sessions, transfers, component, replies_cache = {}, {}, nil, {};
+
+local proxy_port = config_get(host, "core", "proxy65_port") or 5000;
+local proxy_interface = config_get(host, "core", "proxy65_interface") or "*";
+local proxy_address = config_get(host, "core", "proxy65_address") or (proxy_interface ~= "*" and proxy_interface) or host;
+local proxy_acl = config_get(host, "core", "proxy65_acl");
+
+local connlistener = { default_port = proxy_port, default_interface = proxy_interface, default_mode = "*a" };
+
+function connlistener.onincoming(conn, data)
+	local session = sessions[conn] or {};
+	
+	if session.setup == nil and data ~= nil and data:sub(1):byte() == 0x05 and data:len() > 2 then
+		local nmethods = data:sub(2):byte();
+		local methods = data:sub(3);
+		local supported = false;
+		for i=1, nmethods, 1 do
+			if(methods:sub(i):byte() == 0x00) then -- 0x00 == method: NO AUTH
+				supported = true;
+				break;
+			end
+		end
+		if(supported) then
+			module:log("debug", "new session found ... ")
+			session.setup = true;
+			sessions[conn] = session;
+			conn:write(string.char(5, 0));
+		end
+		return;
+	end
+	if session.setup then
+		if session.sha ~= nil and transfers[session.sha] ~= nil then
+			local sha = session.sha;
+			if transfers[sha].activated == true and transfers[sha].initiator == conn and transfers[sha].target ~= nil then
+				transfers[sha].target:write(data);
+				return;
+			end
+		end
+		if data ~= nil and data:len() == 0x2F and  -- 40 == length of SHA1 HASH, and 7 other bytes => 47 => 0x2F
+			data:sub(1):byte() == 0x05 and -- SOCKS5 has 5 in first byte
+			data:sub(2):byte() == 0x01 and -- CMD must be 1
+			data:sub(3):byte() == 0x00 and -- RSV must be 0
+			data:sub(4):byte() == 0x03 and -- ATYP must be 3
+			data:sub(5):byte() == 40 and -- SHA1 HASH length must be 40 (0x28)
+			data:sub(-2):byte() == 0x00 and -- PORT must be 0, size 2 byte
+			data:sub(-1):byte() == 0x00 		
+		then
+			local sha = data:sub(6, 45); -- second param is not count! it's the ending index (included!)
+			if transfers[sha] == nil then
+				transfers[sha] = {};
+				transfers[sha].activated = false;
+				transfers[sha].target = conn;
+				session.sha = sha;
+				module:log("debug", "target connected ... ");
+			elseif transfers[sha].target ~= nil then
+				transfers[sha].initiator = conn;
+				session.sha = sha;
+				module:log("debug", "initiator connected ... ");
+			end
+			conn:write(string.char(5, 0, 0, 3, sha:len()) .. sha .. string.char(0, 0)); -- VER, REP, RSV, ATYP, BND.ADDR (sha), BND.PORT (2 Byte)
+		else
+			log:module("warn", "Neither data transfer nor initial connect of a participator of a transfer.")
+			conn.close();
+		end
+	else
+		if data ~= nil then
+			module:log("warn", "unknown connection with no authentication data -> closing it");
+			conn.close();
+		end
+	end
+end
+
+function connlistener.ondisconnect(conn, err)
+	local session = sessions[conn];
+	if session then
+		if session.sha and transfers[session.sha] then
+			local initiator, target = transfers[session.sha].initiator, transfers[session.sha].target;
+			if initiator == conn and target ~= nil then
+				target.close();
+			elseif target == conn and initiator ~= nil then
+			 	initiator.close();
+			end
+			transfers[session.sha] = nil;
+		end
+		-- Clean up any session-related stuff here
+		sessions[conn] = nil;
+	end
+end
+
+local function get_disco_info(stanza)
+	local reply = replies_cache.disco_info;
+	if reply == nil then
+	 	reply = st.iq({type='result', from=host}):query("http://jabber.org/protocol/disco#info")
+			:tag("identity", {category='proxy', type='bytestreams', name=name}):up()
+			:tag("feature", {var="http://jabber.org/protocol/bytestreams"});
+		replies_cache.disco_info = reply;
+	end
+
+	reply.attr.id = stanza.attr.id;
+	reply.attr.to = stanza.attr.from;
+	return reply;
+end
+
+local function get_disco_items(stanza)
+	local reply = replies_cache.disco_items;
+	if reply == nil then
+	 	reply = st.iq({type='result', from=host}):query("http://jabber.org/protocol/disco#items");
+		replies_cache.disco_items = reply;
+	end
+	
+	reply.attr.id = stanza.attr.id;
+	reply.attr.to = stanza.attr.from;
+	return reply;
+end
+
+local function get_stream_host(origin, stanza)
+	local reply = replies_cache.stream_host;
+	local err_reply = replies_cache.stream_host_err;
+	local sid = stanza.tags[1].attr.sid;
+	local allow = false;
+	local jid_node, jid_host, jid_resource = jid_split(stanza.attr.from);
+	
+	if stanza.attr.from == nil then
+		jid_node = origin.username;
+		jid_host = origin.host;
+		jid_resource = origin.resource;
+	end
+	
+	if proxy_acl and #proxy_acl > 0 then
+		if host ~= nil then -- at least a domain is needed.
+			for _, acl in ipairs(proxy_acl) do
+				local acl_node, acl_host, acl_resource = jid_split(acl);
+				if ((acl_node ~= nil and acl_node == jid_node) or acl_node == nil) and
+				   ((acl_host ~= nil and acl_host == jid_host) or acl_host == nil) and
+				   ((acl_resource ~= nil and acl_resource == jid_resource) or acl_resource == nil) then
+					allow = true;
+				end
+			end
+		end
+	else
+		allow = true;
+	end
+	if allow == true then
+		if reply == nil then
+			reply = st.iq({type="result", from=host})
+				:query("http://jabber.org/protocol/bytestreams")
+				:tag("streamhost", {jid=host, host=proxy_address, port=proxy_port});
+			replies_cache.stream_host = reply;
+		end
+	else
+		module:log("warn", "Denying use of proxy for %s", tostring(jid_join(jid_node, jid_host, jid_resource)));
+		if err_reply == nil then
+			err_reply = st.iq({type="error", from=host})
+				:query("http://jabber.org/protocol/bytestreams")
+				:tag("error", {code='403', type='auth'})
+				:tag("forbidden", {xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'});
+			replies_cache.stream_host_err = err_reply;
+		end
+		reply = err_reply;
+	end
+	reply.attr.id = stanza.attr.id;
+	reply.attr.to = stanza.attr.from;
+	reply.tags[1].attr.sid = sid;
+	return reply;
+end
+
+module.unload = function()
+	componentmanager.deregister_component(host);
+	connlisteners.deregister(module.host .. ':proxy65');
+end
+
+local function set_activation(stanza)
+	local from, to, sid, reply = nil;
+	from = stanza.attr.from;
+	if stanza.tags[1] ~= nil and tostring(stanza.tags[1].name) == "query" then
+		if stanza.tags[1].attr ~= nil then
+			sid = stanza.tags[1].attr.sid;
+		end
+		if stanza.tags[1].tags[1] ~= nil and tostring(stanza.tags[1].tags[1].name) == "activate" then
+			to = stanza.tags[1].tags[1][1];
+		end
+	end
+	if from ~= nil and to ~= nil and sid ~= nil then
+		reply = st.iq({type="result", from=host, to=from});
+		reply.attr.id = stanza.attr.id;
+	end
+	return reply, from, to, sid;
+end
+
+function handle_to_domain(origin, stanza)
+	local to_node, to_host, to_resource = jid_split(stanza.attr.to);
+	if to_node == nil then
+		local type = stanza.attr.type;
+		if type == "error" or type == "result" then return; end
+		if stanza.name == "iq" and type == "get" then
+			local xmlns = stanza.tags[1].attr.xmlns
+			if xmlns == "http://jabber.org/protocol/disco#info" then
+				origin.send(get_disco_info(stanza));
+				return true;
+			elseif xmlns == "http://jabber.org/protocol/disco#items" then
+				origin.send(get_disco_items(stanza));
+				return true;
+			elseif xmlns == "http://jabber.org/protocol/bytestreams" then
+				origin.send(get_stream_host(origin, stanza));
+				return true;
+			end
+		elseif stanza.name == "iq" and type == "set" then
+			local reply, from, to, sid = set_activation(stanza);
+			if reply ~= nil and from ~= nil and to ~= nil and sid ~= nil then
+				local sha = sha1(sid .. from .. to, true);
+				if transfers[sha] == nil then
+					module:log("error", "transfers[sha]: nil");
+				elseif(transfers[sha] ~= nil and transfers[sha].initiator ~= nil and transfers[sha].target ~= nil) then
+					origin.send(reply);
+					transfers[sha].activated = true;
+				end
+			else
+				module:log("error", "activation failed: sid: %s, initiator: %s, target: %s", tostring(sid), tostring(from), tostring(to));
+			end
+		end
+	end
+	return;
+end
+
+if not connlisteners.register(module.host .. ':proxy65', connlistener) then
+	error("mod_proxy65: Could not establish a connection listener. Check your configuration please.");
+	error(" one possible cause for this would be that two proxy65 components share the same port.");
+end
+
+connlisteners.start(module.host .. ':proxy65');
+component = componentmanager.register_component(host, handle_to_domain);
--- a/plugins/mod_register.lua	Sun Nov 29 21:32:39 2009 +0100
+++ b/plugins/mod_register.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -43,21 +43,21 @@
 					session:close({condition = "not-authorized", text = "Account deleted"});
 				end
 				-- TODO datamanager should be able to delete all user data itself
-				datamanager.store(username, host, "roster", nil);
 				datamanager.store(username, host, "vcard", nil);
 				datamanager.store(username, host, "private", nil);
 				datamanager.store(username, host, "offline", nil);
-				--local bare = username.."@"..host;
+				local bare = username.."@"..host;
 				for jid, item in pairs(roster) do
-					if jid ~= "pending" then
-						if item.subscription == "both" or item.subscription == "to" then
-							-- TODO unsubscribe
+					if jid and jid ~= "pending" then
+						if item.subscription == "both" or item.subscription == "from" or (roster.pending and roster.pending[jid]) then
+							core_post_stanza(hosts[host], st.presence({type="unsubscribed", from=bare, to=jid}));
 						end
-						if item.subscription == "both" or item.subscription == "from" then
-							-- TODO unsubscribe
+						if item.subscription == "both" or item.subscription == "to" or item.ask then
+							core_post_stanza(hosts[host], st.presence({type="unsubscribe", from=bare, to=jid}));
 						end
 					end
 				end
+				datamanager.store(username, host, "roster", nil);
 				datamanager.store(username, host, "accounts", nil); -- delete accounts datastore at the end
 				module:log("info", "User removed their account: %s@%s", username, host);
 				module:fire_event("user-deregistered", { username = username, host = host, source = "mod_register", session = session });
--- a/plugins/mod_roster.lua	Sun Nov 29 21:32:39 2009 +0100
+++ b/plugins/mod_roster.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -74,18 +74,20 @@
 						if not resource and host then
 							if jid ~= from_node.."@"..from_host then
 								if item.attr.subscription == "remove" then
-									local r_item = session.roster[jid];
+									local roster = session.roster;
+									local r_item = roster[jid];
 									if r_item then
+										local to_bare = node and (node.."@"..host) or host; -- bare JID
+										if r_item.subscription == "both" or r_item.subscription == "from" or (roster.pending and roster.pending[jid]) then
+											core_post_stanza(session, st.presence({type="unsubscribed", from=session.full_jid, to=to_bare}));
+										end
+										if r_item.subscription == "both" or r_item.subscription == "to" or r_item.ask then
+											core_post_stanza(session, st.presence({type="unsubscribe", from=session.full_jid, to=to_bare}));
+										end
 										local success, err_type, err_cond, err_msg = rm_remove_from_roster(session, jid);
 										if success then
 											session.send(st.reply(stanza));
 											rm_roster_push(from_node, from_host, jid);
-											local to_bare = node and (node.."@"..host) or host; -- bare JID
-											if r_item.subscription == "both" or r_item.subscription == "from" then
-												core_post_stanza(session, st.presence({type="unsubscribed", from=session.full_jid, to=to_bare}));
-											elseif r_item.subscription == "both" or r_item.subscription == "to" then
-												core_post_stanza(session, st.presence({type="unsubscribe", from=session.full_jid, to=to_bare}));
-											end
 										else
 											session.send(st.error_reply(stanza, err_type, err_cond, err_msg));
 										end
--- a/plugins/mod_saslauth.lua	Sun Nov 29 21:32:39 2009 +0100
+++ b/plugins/mod_saslauth.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -1,7 +1,7 @@
 -- Prosody IM
 -- Copyright (C) 2008-2009 Matthew Wild
 -- Copyright (C) 2008-2009 Waqas Hussain
--- 
+--
 -- This project is MIT/X11 licensed. Please see the
 -- COPYING file in the source package for more information.
 --
@@ -35,6 +35,27 @@
 
 local new_sasl = require "util.sasl".new;
 
+default_authentication_profile = {
+	plain = function(username, realm)
+			local prepped_username = nodeprep(username);
+			if not prepped_username then
+				log("debug", "NODEprep failed on username: %s", username);
+				return "", nil;
+			end
+			local password = usermanager_get_password(prepped_username, realm);
+			if not password then
+				return "", nil;
+			end
+			return password, true;
+		end
+};
+
+anonymous_authentication_profile = {
+	anonymous = function(username, realm)
+			return true; -- for normal usage you should always return true here
+		end
+}
+
 local function build_reply(status, ret, err_msg)
 	local reply = st.stanza(status, {xmlns = xmlns_sasl});
 	if status == "challenge" then
@@ -54,53 +75,21 @@
 
 local function handle_status(session, status)
 	if status == "failure" then
-		session.sasl_handler = nil;
+		session.sasl_handler = session.sasl_handler:clean_clone();
 	elseif status == "success" then
 		local username = nodeprep(session.sasl_handler.username);
-		session.sasl_handler = nil;
 		if not username then -- TODO move this to sessionmanager
 			module:log("warn", "SASL succeeded but we didn't get a username!");
 			session.sasl_handler = nil;
 			session:reset_stream();
 			return;
-		end 
-		sm_make_authenticated(session, username);
+		end
+		sm_make_authenticated(session, session.sasl_handler.username);
+		session.sasl_handler = nil;
 		session:reset_stream();
 	end
 end
 
-local function credentials_callback(mechanism, ...)
-	if mechanism == "PLAIN" then
-		local username, hostname, password = ...;
-		username = nodeprep(username);
-		if not username then
-			return false;
-		end
-		local response = usermanager_validate_credentials(hostname, username, password, mechanism);
-		if response == nil then
-			return false;
-		else
-			return response;
-		end
-	elseif mechanism == "DIGEST-MD5" then
-		local function func(x) return x; end
-		local node, domain, realm, decoder = ...;
-		local prepped_node = nodeprep(node);
-		if not prepped_node then
-			return func, nil;
-		end
-		local password = usermanager_get_password(prepped_node, domain);
-		if password then
-			if decoder then
-				node, realm, password = decoder(node), decoder(realm), decoder(password);
-			end
-			return func, md5(node..":"..realm..":"..password);
-		else
-			return func, nil;
-		end
-	end
-end
-
 local function sasl_handler(session, stanza)
 	if stanza.name == "auth" then
 		-- FIXME ignoring duplicates because ejabberd does
@@ -111,8 +100,8 @@
 		elseif stanza.attr.mechanism == "ANONYMOUS" then
 			return session.send(build_reply("failure", "mechanism-too-weak"));
 		end
-		session.sasl_handler = new_sasl(stanza.attr.mechanism, session.host, credentials_callback);
-		if not session.sasl_handler then
+		local valid_mechanism = session.sasl_handler:select(stanza.attr.mechanism);
+		if not valid_mechanism then
 			return session.send(build_reply("failure", "invalid-mechanism"));
 		end
 	elseif not session.sasl_handler then
@@ -128,7 +117,7 @@
 			return;
 		end
 	end
-	local status, ret, err_msg = session.sasl_handler:feed(text);
+	local status, ret, err_msg = session.sasl_handler:process(text);
 	handle_status(session, status);
 	local s = build_reply(status, ret, err_msg);
 	log("debug", "sasl reply: %s", tostring(s));
@@ -148,16 +137,18 @@
 				if secure_auth_only and not session.secure then
 					return;
 				end
+				if module:get_option("anonymous_login") then
+					session.sasl_handler = new_sasl(session.host, anonymous_authentication_profile);
+				else
+					session.sasl_handler = new_sasl(session.host, default_authentication_profile);
+					if not (module:get_option("allow_unencrypted_plain_auth")) and not session.secure then
+						session.sasl_handler:forbidden({"PLAIN"});
+					end
+				end
 				features:tag("mechanisms", mechanisms_attr);
-				-- TODO: Provide PLAIN only if TLS is active, this is a SHOULD from the introduction of RFC 4616. This behavior could be overridden via configuration but will issuing a warning or so.
-					if config.get(session.host or "*", "core", "anonymous_login") then
-						features:tag("mechanism"):text("ANONYMOUS"):up();
-					else
-						local mechanisms = usermanager_get_supported_methods(session.host or "*");
-						for k, v in pairs(mechanisms) do
-							features:tag("mechanism"):text(k):up();
-						end
-					end
+				for k, v in pairs(session.sasl_handler:mechanisms()) do
+					features:tag("mechanism"):text(v):up();
+				end
 				features:up();
 			else
 				features:tag("bind", bind_attr):tag("required"):up():up();
--- a/plugins/mod_tls.lua	Sun Nov 29 21:32:39 2009 +0100
+++ b/plugins/mod_tls.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -20,9 +20,9 @@
 				session.send(st.stanza("proceed", { xmlns = xmlns_starttls }));
 				session:reset_stream();
 				if session.host and hosts[session.host].ssl_ctx_in then
-					session.conn.set_sslctx(hosts[session.host].ssl_ctx_in);
+					session.conn:set_sslctx(hosts[session.host].ssl_ctx_in);
 				end
-				session.conn.starttls();
+				session.conn:starttls();
 				session.log("info", "TLS negotiation started...");
 				session.secure = false;
 			else
@@ -37,9 +37,9 @@
 				session.sends2s(st.stanza("proceed", { xmlns = xmlns_starttls }));
 				session:reset_stream();
 				if session.to_host and hosts[session.to_host].ssl_ctx_in then
-					session.conn.set_sslctx(hosts[session.to_host].ssl_ctx_in);
+					session.conn:set_sslctx(hosts[session.to_host].ssl_ctx_in);
 				end
-				session.conn.starttls();
+				session.conn:starttls();
 				session.log("info", "TLS negotiation started for incoming s2s...");
 				session.secure = false;
 			else
@@ -91,7 +91,7 @@
 			module:log("debug", "Proceeding with TLS on s2sout...");
 			local format, to_host, from_host = string.format, session.to_host, session.from_host;
 			session:reset_stream();
-			session.conn.starttls(true);
+			session.conn:starttls(true);
 			session.secure = false;
 			return true;
 		end);
--- a/plugins/muc/muc.lib.lua	Sun Nov 29 21:32:39 2009 +0100
+++ b/plugins/muc/muc.lib.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -128,19 +128,21 @@
 	end
 end
 function room_mt:broadcast_message(stanza, historic)
+	local to = stanza.attr.to;
 	for occupant, o_data in pairs(self._occupants) do
 		for jid in pairs(o_data.sessions) do
 			stanza.attr.to = jid;
 			self:_route_stanza(stanza);
 		end
 	end
+	stanza.attr.to = to;
 	if historic then -- add to history
 		local history = self._data['history'];
 		if not history then history = {}; self._data['history'] = history; end
-		-- stanza = st.clone(stanza);
+		stanza = st.clone(stanza);
 		stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = muc_domain, stamp = datetime.datetime()}):up(); -- XEP-0203
 		stanza:tag("x", {xmlns = "jabber:x:delay", from = muc_domain, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated)
-		t_insert(history, st.clone(st.preserialize(stanza)));
+		t_insert(history, st.preserialize(stanza));
 		while #history > history_length do t_remove(history, 1) end
 	end
 end
@@ -387,51 +389,70 @@
 	end
 end
 
-function room_mt:handle_form(origin, stanza)
-	if self:get_affiliation(stanza.attr.from) ~= "owner" then origin.send(st.error_reply(stanza, "auth", "forbidden")); return; end
-	if stanza.attr.type == "get" then
-		local title = "Configuration for "..self.jid;
-		origin.send(st.reply(stanza):query("http://jabber.org/protocol/muc#owner")
-			:tag("x", {xmlns='jabber:x:data', type='form'})
-				:tag("title"):text(title):up()
-				:tag("instructions"):text(title):up()
-				:tag("field", {type='hidden', var='FORM_TYPE'}):tag("value"):text("http://jabber.org/protocol/muc#roomconfig"):up():up()
-				:tag("field", {type='boolean', label='Make Room Persistent?', var='muc#roomconfig_persistentroom'})
-					:tag("value"):text(self._data.persistent and "1" or "0"):up()
-				:up()
-				:tag("field", {type='boolean', label='Make Room Publicly Searchable?', var='muc#roomconfig_publicroom'})
-					:tag("value"):text(self._data.hidden and "0" or "1"):up()
-				:up()
-		);
-	elseif stanza.attr.type == "set" then
-		local query = stanza.tags[1];
-		local form;
-		for _, tag in ipairs(query.tags) do if tag.name == "x" and tag.attr.xmlns == "jabber:x:data" then form = tag; break; end end
-		if not form then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); return; end
-		if form.attr.type == "cancel" then origin.send(st.reply(stanza)); return; end
-		if form.attr.type ~= "submit" then origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
-		local fields = {};
-		for _, field in pairs(form.tags) do
-			if field.name == "field" and field.attr.var and field.tags[1].name == "value" and #field.tags[1].tags == 0 then
-				fields[field.attr.var] = field.tags[1][1] or "";
-			end
+function room_mt:send_form(origin, stanza)
+	local title = "Configuration for "..self.jid;
+	origin.send(st.reply(stanza):query("http://jabber.org/protocol/muc#owner")
+		:tag("x", {xmlns='jabber:x:data', type='form'})
+			:tag("title"):text(title):up()
+			:tag("instructions"):text(title):up()
+			:tag("field", {type='hidden', var='FORM_TYPE'}):tag("value"):text("http://jabber.org/protocol/muc#roomconfig"):up():up()
+			:tag("field", {type='boolean', label='Make Room Persistent?', var='muc#roomconfig_persistentroom'})
+				:tag("value"):text(self._data.persistent and "1" or "0"):up()
+			:up()
+			:tag("field", {type='boolean', label='Make Room Publicly Searchable?', var='muc#roomconfig_publicroom'})
+				:tag("value"):text(self._data.hidden and "0" or "1"):up()
+			:up()
+	);
+end
+
+function room_mt:process_form(origin, stanza)
+	local query = stanza.tags[1];
+	local form;
+	for _, tag in ipairs(query.tags) do if tag.name == "x" and tag.attr.xmlns == "jabber:x:data" then form = tag; break; end end
+	if not form then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); return; end
+	if form.attr.type == "cancel" then origin.send(st.reply(stanza)); return; end
+	if form.attr.type ~= "submit" then origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
+	local fields = {};
+	for _, field in pairs(form.tags) do
+		if field.name == "field" and field.attr.var and field.tags[1].name == "value" and #field.tags[1].tags == 0 then
+			fields[field.attr.var] = field.tags[1][1] or "";
 		end
-		if fields.FORM_TYPE ~= "http://jabber.org/protocol/muc#roomconfig" then origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
+	end
+	if fields.FORM_TYPE ~= "http://jabber.org/protocol/muc#roomconfig" then origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
+
+	local persistent = fields['muc#roomconfig_persistentroom'];
+	if persistent == "0" or persistent == "false" then persistent = nil; elseif persistent == "1" or persistent == "true" then persistent = true;
+	else origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
+	self._data.persistent = persistent;
+	module:log("debug", "persistent=%s", tostring(persistent));
 
-		local persistent = fields['muc#roomconfig_persistentroom'];
-		if persistent == "0" or persistent == "false" then persistent = nil; elseif persistent == "1" or persistent == "true" then persistent = true;
-		else origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
-		self._data.persistent = persistent;
-		module:log("debug", "persistent=%s", tostring(persistent));
+	local public = fields['muc#roomconfig_publicroom'];
+	if public == "0" or public == "false" then public = nil; elseif public == "1" or public == "true" then public = true;
+	else origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
+	self._data.hidden = not public and true or nil;
+
+	if self.save then self:save(true); end
+	origin.send(st.reply(stanza));
+end
 
-		local public = fields['muc#roomconfig_publicroom'];
-		if public == "0" or public == "false" then public = nil; elseif public == "1" or public == "true" then public = true;
-		else origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
-		self._data.hidden = not public and true or nil;
-
-		if self.save then self:save(true); end
-		origin.send(st.reply(stanza));
+function room_mt:destroy(newjid, reason, password)
+	local pr = st.presence({type = "unavailable"})
+		:tag("x", {xmlns = "http://jabber.org/protocol/muc#user"})
+			:tag("item", { affiliation='none', role='none' }):up()
+			:tag("destroy", {jid=newjid})
+	if reason then pr:tag("reason"):text(reason):up(); end
+	if password then pr:tag("password"):text(password):up(); end
+	for nick, occupant in pairs(self._occupants) do
+		pr.attr.from = nick;
+		for jid in pairs(occupant.sessions) do
+			pr.attr.to = jid;
+			self:_route_stanza(pr);
+			self._jid_nick[jid] = nil;
+		end
+		self._occupants[nick] = nil;
 	end
+	self._data.persistent = nil;
+	if self.save then self:save(true); end
 end
 
 function room_mt:handle_to_room(origin, stanza) -- presence changes and groupchat messages, along with disco/etc
@@ -509,7 +530,30 @@
 				origin.send(st.error_reply(stanza, "cancel", "bad-request"));
 			end
 		elseif xmlns == "http://jabber.org/protocol/muc#owner" and (type == "get" or type == "set") and stanza.tags[1].name == "query" then
-			self:handle_form(origin, stanza);
+			if self:get_affiliation(stanza.attr.from) ~= "owner" then
+				origin.send(st.error_reply(stanza, "auth", "forbidden"));
+			elseif stanza.attr.type == "get" then
+				self:send_form(origin, stanza);
+			elseif stanza.attr.type == "set" then
+				local child = stanza.tags[1].tags[1];
+				if not child then
+					origin.send(st.error_reply(stanza, "auth", "bad-request"));
+				elseif child.name == "destroy" then
+					local newjid = child.attr.jid;
+					local reason, password;
+					for _,tag in ipairs(child.tags) do
+						if tag.name == "reason" then
+							reason = #tag.tags == 0 and tag[1];
+						elseif tag.name == "password" then
+							password = #tag.tags == 0 and tag[1];
+						end
+					end
+					self:destroy(newjid, reason, password);
+					origin.send(st.reply(stanza));
+				else
+					self:process_form(origin, stanza);
+				end
+			end
 		elseif type == "set" or type == "get" then
 			origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
 		end
@@ -517,17 +561,26 @@
 		local from, to = stanza.attr.from, stanza.attr.to;
 		local room = jid_bare(to);
 		local current_nick = self._jid_nick[from];
-		if not current_nick then -- not in room
+		local occupant = self._occupants[current_nick];
+		if not occupant then -- not in room
 			origin.send(st.error_reply(stanza, "cancel", "not-acceptable"));
+		elseif occupant.role == "visitor" then
+			origin.send(st.error_reply(stanza, "cancel", "forbidden"));
 		else
 			local from = stanza.attr.from;
 			stanza.attr.from = current_nick;
 			local subject = getText(stanza, {"subject"});
 			if subject then
-				self:set_subject(current_nick, subject); -- TODO use broadcast_message_stanza
+				if occupant.role == "moderator" then
+					self:set_subject(current_nick, subject); -- TODO use broadcast_message_stanza
+				else
+					stanza.attr.from = from;
+					origin.send(st.error_reply(stanza, "cancel", "forbidden"));
+				end
 			else
 				self:broadcast_message(stanza, true);
 			end
+			stanza.attr.from = from;
 		end
 	elseif stanza.name == "message" and type == "error" and is_kickable_error(stanza) then
 		local current_nick = self._jid_nick[stanza.attr.from];
--- a/prosody	Sun Nov 29 21:32:39 2009 +0100
+++ b/prosody	Sun Nov 29 21:33:37 2009 +0100
@@ -14,7 +14,7 @@
 CFG_PLUGINDIR=os.getenv("PROSODY_PLUGINDIR");
 CFG_DATADIR=os.getenv("PROSODY_DATADIR");
 
--- -- -- -- -- -- -- ---- -- -- -- -- -- -- -- --
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
 
 if CFG_SOURCEDIR then
 	package.path = CFG_SOURCEDIR.."/?.lua;"..package.path;
@@ -58,7 +58,27 @@
 function read_config()
 	-- TODO: Check for other formats when we add support for them
 	-- Use lfs? Make a new conf/ dir?
-	local ok, level, err = config.load((CFG_CONFIGDIR or ".").."/prosody.cfg.lua");
+	local filenames = {};
+	
+	local filename;
+	if arg[1] == "--config" and arg[2] then
+		table.insert(filenames, arg[2]);
+		if CFG_CONFIGDIR then
+			table.insert(filenames, CFG_CONFIGDIR.."/"..arg[2]);
+		end
+	else
+		table.insert(filenames, (CFG_CONFIGDIR or ".").."/prosody.cfg.lua");
+	end
+	for _,_filename in ipairs(filenames) do
+		filename = _filename;
+		local file = io.open(filename);
+		if file then
+			file:close();
+			CFG_CONFIGDIR = filename:match("^(.*)[\\/][^\\/]*$");
+			break;
+		end
+	end
+	local ok, level, err = config.load(filename);
 	if not ok then
 		print("\n");
 		print("**************************");
@@ -82,13 +102,13 @@
 end
 
 function load_libraries()
-	--- Initialize logging
+	-- Initialize logging
 	require "core.loggingmanager"
 	
-	--- Check runtime dependencies
+	-- Check runtime dependencies
 	require "util.dependencies"
 	
-	--- Load socket framework
+	-- Load socket framework
 	server = require "net.server"
 end	
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/modulemanager_option_conversion.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -0,0 +1,55 @@
+package.path = "../?.lua;"..package.path;
+
+local api = require "core.modulemanager".api;
+
+local module = setmetatable({}, {__index = api});
+local opt = nil;
+function module:log() end
+function module:get_option(name)
+	if name == "opt" then
+		return opt;
+	else
+		return nil;
+	end
+end
+
+function test_value(value, returns)
+	opt = value;
+	assert(module:get_option_number("opt") == returns.number, "number doesn't match");
+	assert(module:get_option_string("opt") == returns.string, "string doesn't match");
+	assert(module:get_option_boolean("opt") == returns.boolean, "boolean doesn't match");
+	
+	if type(returns.array) == "table" then
+		local target_array, returned_array = returns.array, module:get_option_array("opt");
+		assert(#target_array == #returned_array, "array length doesn't match");
+		for i=1,#target_array do
+			assert(target_array[i] == returned_array[i], "array item doesn't match");
+		end
+	else
+		assert(module:get_option_array("opt") == returns.array, "array is returned (not nil)");
+	end
+	
+	if type(returns.set) == "table" then
+		local target_items, returned_items = set.new(returns.set), module:get_option_set("opt");
+		assert(target_items == returned_items, "set doesn't match");
+	else
+		assert(module:get_option_set("opt") == returns.set, "set is returned (not nil)");
+	end
+end
+
+test_value(nil, {});
+
+test_value(true, { boolean = true, string = "true", array = {true}, set = {true} });
+test_value(false, { boolean = false, string = "false", array = {false}, set = {false} });
+test_value("true", { boolean = true, string = "true", array = {"true"}, set = {"true"} });
+test_value("false", { boolean = false, string = "false", array = {"false"}, set = {"false"} });
+test_value(1, { boolean = true, string = "1", array = {1}, set = {1}, number = 1 });
+test_value(0, { boolean = false, string = "0", array = {0}, set = {0}, number = 0 });
+
+test_value("hello world", { string = "hello world", array = {"hello world"}, set = {"hello world"} });
+test_value(1234, { string = "1234", number = 1234, array = {1234}, set = {1234} });
+
+test_value({1, 2, 3}, { boolean = true, string = "1", number = 1, array = {1, 2, 3}, set = {1, 2, 3} });
+test_value({1, 2, 3, 3, 4}, {boolean = true, string = "1", number = 1, array = {1, 2, 3, 3, 4}, set = {1, 2, 3, 4} });
+test_value({0, 1, 2, 3}, { boolean = false, string = "0", number = 0, array = {0, 1, 2, 3}, set = {0, 1, 2, 3} });
+
--- a/tests/test.lua	Sun Nov 29 21:32:39 2009 +0100
+++ b/tests/test.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -16,7 +16,7 @@
 	dotest "core.s2smanager"
 	dotest "core.configmanager"
 	dotest "util.stanza"
-		
+	
 	dosingletest("test_sasl.lua", "latin1toutf8");
 end
 
@@ -106,7 +106,9 @@
 end
 
 function dotest(unitname)
-	local tests = setmetatable({}, { __index = _realG });
+	local _fakeG = setmetatable({}, {__index = _realG});
+	_fakeG._G = _fakeG;
+	local tests = setmetatable({}, { __index = _fakeG });
 	tests.__unit = unitname;
 	local chunk, err = loadfile("test_"..unitname:gsub("%.", "_")..".lua");
 	if not chunk then
@@ -120,19 +122,20 @@
 		print("WARNING: ", "Failed to initialise tests for "..unitname, err);
 		return;
 	end
-	
 	if tests.env then setmetatable(tests.env, { __index = _realG }); end
-	local unit = setmetatable({}, { __index = setmetatable({ _G = tests.env or _G }, { __index = tests.env or _G }) });
-	unit._G = unit; _realG._G = unit;
+	local unit = setmetatable({}, { __index = setmetatable({ _G = tests.env or _fakeG }, { __index = tests.env or _fakeG }) });
 	local fn = "../"..unitname:gsub("%.", "/")..".lua";
 	local chunk, err = loadfile(fn);
 	if not chunk then
 		print("WARNING: ", "Failed to load module: "..unitname, err);
 		return;
 	end
-
+	
+	local oldmodule, old_M = _fakeG.module, _fakeG._M;
+	_fakeG.module = function () _M = _G end
 	setfenv(chunk, unit);
 	local success, err = pcall(chunk);
+	_fakeG.module, _fakeG._M = oldmodule, old_M;
 	if not success then
 		print("WARNING: ", "Failed to initialise module: "..unitname, err);
 		return;
@@ -149,6 +152,9 @@
 				print("WARNING: ", unitname.."."..name.." has no test!");
 			end
 		else
+			if verbosity >= 4 then
+				print("INFO: ", "Testing "..unitname.."."..name);
+			end
 			local line_hook, line_info = new_line_coverage_monitor(fn);
 			debug.sethook(line_hook, "l")
 			local success, ret = pcall(test, f, unit);
--- a/tests/test_util_jid.lua	Sun Nov 29 21:32:39 2009 +0100
+++ b/tests/test_util_jid.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -6,6 +6,16 @@
 -- COPYING file in the source package for more information.
 --
 
+function join(join)
+	assert_equal(join("a", "b", "c"), "a@b/c", "builds full JID");
+	assert_equal(join("a", "b", nil), "a@b", "builds bare JID");
+	assert_equal(join(nil, "b", "c"), "b/c", "builds full host JID");
+	assert_equal(join(nil, "b", nil), "b", "builds bare host JID");
+	assert_equal(join(nil, nil, nil), nil, "invalid JID is nil");
+	assert_equal(join("a", nil, nil), nil, "invalid JID is nil");
+	assert_equal(join(nil, nil, "c"), nil, "invalid JID is nil");
+	assert_equal(join("a", nil, "c"), nil, "invalid JID is nil");
+end
 
 
 function split(split)
@@ -43,3 +53,4 @@
 	assert_equal(bare("user@@host/resource"), nil, "invalid JID is nil");
 	assert_equal(bare("user@host/"), nil, "invalid JID is nil");
 end
+
--- a/util/dataforms.lua	Sun Nov 29 21:32:39 2009 +0100
+++ b/util/dataforms.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -93,7 +93,13 @@
 	local data = {};
 	
 	for field_tag in stanza:childtags() do
-		local field_type = field_tag.attr.type;
+		local field_type;
+		for n, field in ipairs(layout) do
+			if field.name == field_tag.attr.var then
+				field_type = field.type;
+				break;
+			end
+		end
 		
 		local reader = field_readers[field_type];
 		if reader then
--- a/util/datamanager.lua	Sun Nov 29 21:32:39 2009 +0100
+++ b/util/datamanager.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -15,13 +15,13 @@
 local log = require "util.logger".init("datamanager");
 local io_open = io.open;
 local os_remove = os.remove;
-local io_popen = io.popen;
 local tostring, tonumber = tostring, tonumber;
 local error = error;
 local next = next;
 local t_insert = table.insert;
 local append = require "util.serialization".append;
 local path_separator = "/"; if os.getenv("WINDIR") then path_separator = "\\" end
+local lfs_mkdir = require "lfs".mkdir;
 
 module "datamanager"
 
@@ -43,7 +43,7 @@
 local function mkdir(path)
 	path = path:gsub("/", path_separator); -- TODO as an optimization, do this during path creation rather than here
 	if not _mkdir[path] then
-		local x = io_popen("mkdir \""..path.."\" 2>&1"):read("*a");
+		lfs_mkdir(path);
 		_mkdir[path] = true;
 	end
 	return path;
--- a/util/dependencies.lua	Sun Nov 29 21:32:39 2009 +0100
+++ b/util/dependencies.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -17,8 +17,12 @@
 	print("Prosody was unable to find "..tostring(name));
 	print("This package can be obtained in the following ways:");
 	print("");
-	for k,v in pairs(sources) do
-		print("", k, v);
+	local longest_platform = 0;
+	for platform in pairs(sources) do
+		longest_platform = math.max(longest_platform, #platform);
+	end
+	for platform, source in pairs(sources) do
+		print("", platform..":"..(" "):rep(4+longest_platform-#platform)..source);
 	end
 	print("");
 	print(msg or (name.." is required for Prosody to run, so we will now exit."));
@@ -30,24 +34,51 @@
 local lxp = softreq "lxp"
 
 if not lxp then
-	missingdep("luaexpat", { ["Ubuntu 8.04 (Hardy)"] = "sudo apt-get install liblua5.1-expat0"; ["luarocks"] = "luarocks install luaexpat"; });
+	missingdep("luaexpat", {
+			["Debian/Ubuntu"] = "sudo apt-get install liblua5.1-expat0";
+			["luarocks"] = "luarocks install luaexpat";
+			["Source"] = "http://www.keplerproject.org/luaexpat/";
+		});
 	fatal = true;
 end
 
 local socket = softreq "socket"
 
 if not socket then
-	missingdep("luasocket", { ["Ubuntu 8.04 (Hardy)"] = "sudo apt-get install liblua5.1-socket2"; ["luarocks"] = "luarocks install luasocket"; });
+	missingdep("luasocket", {
+			["Debian/Ubuntu"] = "sudo apt-get install liblua5.1-socket2";
+			["luarocks"] = "luarocks install luasocket";
+			["Source"] = "http://www.tecgraf.puc-rio.br/~diego/professional/luasocket/";
+		});
 	fatal = true;
 end
 	
+local lfs, err = softreq "lfs"
+if not lfs then
+	missingdep("luafilesystem", {
+			["luarocks"] = "luarocks install luafilesystem";
+	 		["Debian/Ubuntu"] = "sudo apt-get install liblua5.1-luafilesystem0";
+	 		["Source"] = "http://www.keplerproject.org/luafilesystem/";
+	 	});
+	fatal = true;
+end
+
 local ssl = softreq "ssl"
 
 if not ssl then
 	if config.get("*", "core", "run_without_ssl") then
 		log("warn", "Running without SSL support because run_without_ssl is defined in the config");
 	else
-		missingdep("LuaSec", { ["Source"] = "http://www.inf.puc-rio.br/~brunoos/luasec/" }, "SSL/TLS support will not be available");
+		missingdep("LuaSec", {
+				["Debian/Ubuntu"] = "http://prosody.im/download/start#debian_and_ubuntu";
+				["luarocks"] = "luarocks install luasec";
+				["Source"] = "http://www.inf.puc-rio.br/~brunoos/luasec/";
+			}, "SSL/TLS support will not be available");
+	end
+else
+	local major, minor, veryminor, patched = ssl._VERSION:match("(%d+)%.(%d+)%.?(%d*)(M?)");
+	if not major or ((tonumber(major) == 0 and (tonumber(minor) or 0) <= 3 and (tonumber(veryminor) or 0) <= 2) and patched ~= "M") then
+		log("error", "This version of LuaSec contains a known bug that causes disconnects, see http://prosody.im/doc/depends");
 	end
 end
 
--- a/util/jid.lua	Sun Nov 29 21:32:39 2009 +0100
+++ b/util/jid.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -65,4 +65,17 @@
 	return host;
 end
 
+function join(node, host, resource)
+	if node and host and resource then
+		return node.."@"..host.."/"..resource;
+	elseif node and host then
+		return node.."@"..host;
+	elseif host and resource then
+		return host.."/"..resource;
+	elseif host then
+		return host;
+	end
+	return nil; -- Invalid JID
+end
+
 return _M;
--- a/util/sasl.lua	Sun Nov 29 21:32:39 2009 +0100
+++ b/util/sasl.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -14,258 +14,140 @@
 
 local md5 = require "util.hashes".md5;
 local log = require "util.logger".init("sasl");
+local st = require "util.stanza";
+local set = require "util.set";
+local array = require "util.array";
+local to_unicode = require "util.encodings".idna.to_unicode;
+
 local tostring = tostring;
-local st = require "util.stanza";
-local generate_uuid = require "util.uuid".generate;
+local pairs, ipairs = pairs, ipairs;
 local t_insert, t_concat = table.insert, table.concat;
-local to_byte, to_char = string.byte, string.char;
-local to_unicode = require "util.encodings".idna.to_unicode;
 local s_match = string.match;
-local gmatch = string.gmatch
-local string = string
-local math = require "math"
 local type = type
 local error = error
-local print = print
+local setmetatable = setmetatable;
+local assert = assert;
+local require = require;
 
+require "util.iterators"
+local keys = keys
+
+local array = require "util.array"
 module "sasl"
 
--- Credentials handler:
---   Arguments: ("PLAIN", user, host, password)
---   Returns: true (success) | false (fail) | nil (user unknown)
-local function new_plain(realm, credentials_handler)
-	local object = { mechanism = "PLAIN", realm = realm, credentials_handler = credentials_handler}
-	function object.feed(self, message)
-		if message == "" or message == nil then return "failure", "malformed-request" end
-		local response = message
-		local authorization = s_match(response, "([^%z]+)")
-		local authentication = s_match(response, "%z([^%z]+)%z")
-		local password = s_match(response, "%z[^%z]+%z([^%z]+)")
-
-    if authentication == nil or password == nil then return "failure", "malformed-request" end
-    self.username = authentication
-    local auth_success = self.credentials_handler("PLAIN", self.username, self.realm, password)
+--[[
+Authentication Backend Prototypes:
 
-    if auth_success then
-      return "success"
-    elseif auth_success == nil then
-      return "failure", "account-disabled"
-    else
-      return "failure", "not-authorized"
-    end
-  end
-  return object
-end
+state = false : disabled
+state = true : enabled
+state = nil : non-existant
 
--- credentials_handler:
---   Arguments: (mechanism, node, domain, realm, decoder)
---   Returns: Password encoding, (plaintext) password
--- implementing RFC 2831
-local function new_digest_md5(realm, credentials_handler)
-	--TODO complete support for authzid
-
-	local function serialize(message)
-		local data = ""
-
-		if type(message) ~= "table" then error("serialize needs an argument of type table.") end
-
-		-- testing all possible values
-		if message["realm"] then data = data..[[realm="]]..message.realm..[[",]] end
-		if message["nonce"] then data = data..[[nonce="]]..message.nonce..[[",]] end
-		if message["qop"] then data = data..[[qop="]]..message.qop..[[",]] end
-		if message["charset"] then data = data..[[charset=]]..message.charset.."," end
-		if message["algorithm"] then data = data..[[algorithm=]]..message.algorithm.."," end
-		if message["rspauth"] then data = data..[[rspauth=]]..message.rspauth.."," end
-		data = data:gsub(",$", "")
-		return data
+plain:
+	function(username, realm)
+		return password, state;
 	end
 
-	local function utf8tolatin1ifpossible(passwd)
-		local i = 1;
-		while i <= #passwd do
-			local passwd_i = to_byte(passwd:sub(i, i));
-			if passwd_i > 0x7F then
-				if passwd_i < 0xC0 or passwd_i > 0xC3 then
-					return passwd;
-				end
-				i = i + 1;
-				passwd_i = to_byte(passwd:sub(i, i));
-				if passwd_i < 0x80 or passwd_i > 0xBF then
-					return passwd;
-				end
-			end
-			i = i + 1;
-		end
+plain-test:
+	function(username, realm, password)
+		return true or false, state;
+	end
 
-		local p = {};
-		local j = 0;
-		i = 1;
-		while (i <= #passwd) do
-			local passwd_i = to_byte(passwd:sub(i, i));
-			if passwd_i > 0x7F then
-				i = i + 1;
-				local passwd_i_1 = to_byte(passwd:sub(i, i));
-				t_insert(p, to_char(passwd_i%4*64 + passwd_i_1%64)); -- I'm so clever
-			else
-				t_insert(p, to_char(passwd_i));
-			end
-			i = i + 1;
-		end
-		return t_concat(p);
-	end
-	local function latin1toutf8(str)
-		local p = {};
-		for ch in gmatch(str, ".") do
-			ch = to_byte(ch);
-			if (ch < 0x80) then
-				t_insert(p, to_char(ch));
-			elseif (ch < 0xC0) then
-				t_insert(p, to_char(0xC2, ch));
-			else
-				t_insert(p, to_char(0xC3, ch - 64));
-			end
-		end
-		return t_concat(p);
-	end
-	local function parse(data)
-		local message = {}
-		-- COMPAT: %z in the pattern to work around jwchat bug (sends "charset=utf-8\0")
-		for k, v in gmatch(data, [[([%w%-]+)="?([^",%z]*)"?,?]]) do -- FIXME The hacky regex makes me shudder
-			message[k] = v;
-		end
-		return message;
+digest-md5:
+	function(username, domain, realm, encoding) -- domain and realm are usually the same; for some broken
+												-- implementations it's not
+		return digesthash, state;
 	end
 
-	local object = { mechanism = "DIGEST-MD5", realm = realm, credentials_handler = credentials_handler};
-
-	object.nonce = generate_uuid();
-	object.step = 0;
-	object.nonce_count = {};
+digest-md5-test:
+	function(username, domain, realm, encoding, digesthash)
+		return true or false, state;
+	end
+]]
 
-	function object.feed(self, message)
-		self.step = self.step + 1;
-		if (self.step == 1) then
-			local challenge = serialize({	nonce = object.nonce,
-											qop = "auth",
-											charset = "utf-8",
-											algorithm = "md5-sess",
-											realm = self.realm});
-			return "challenge", challenge;
-		elseif (self.step == 2) then
-			local response = parse(message);
-			-- check for replay attack
-			if response["nc"] then
-				if self.nonce_count[response["nc"]] then return "failure", "not-authorized" end
-			end
+local method = {};
+method.__index = method;
+local mechanisms = {};
+local backend_mechanism = {};
 
-			-- check for username, it's REQUIRED by RFC 2831
-			if not response["username"] then
-				return "failure", "malformed-request";
-			end
-			self["username"] = response["username"];
-
-			-- check for nonce, ...
-			if not response["nonce"] then
-				return "failure", "malformed-request";
-			else
-				-- check if it's the right nonce
-				if response["nonce"] ~= tostring(self.nonce) then return "failure", "malformed-request" end
-			end
-
-			if not response["cnonce"] then return "failure", "malformed-request", "Missing entry for cnonce in SASL message." end
-			if not response["qop"] then response["qop"] = "auth" end
-
-			if response["realm"] == nil or response["realm"] == "" then
-				response["realm"] = "";
-			elseif response["realm"] ~= self.realm then
-				return "failure", "not-authorized", "Incorrect realm value";
-			end
-
-			local decoder;
-			if response["charset"] == nil then
-				decoder = utf8tolatin1ifpossible;
-			elseif response["charset"] ~= "utf-8" then
-				return "failure", "incorrect-encoding", "The client's response uses "..response["charset"].." for encoding with isn't supported by sasl.lua. Supported encodings are latin or utf-8.";
-			end
+-- register a new SASL mechanims
+local function registerMechanism(name, backends, f)
+	assert(type(name) == "string", "Parameter name MUST be a string.");
+	assert(type(backends) == "string" or type(backends) == "table", "Parameter backends MUST be either a string or a table.");
+	assert(type(f) == "function", "Parameter f MUST be a function.");
+	mechanisms[name] = f
+	for _, backend_name in ipairs(backends) do
+		if backend_mechanism[backend_name] == nil then backend_mechanism[backend_name] = {}; end
+		t_insert(backend_mechanism[backend_name], name);
+	end
+end
 
-			local domain = "";
-			local protocol = "";
-			if response["digest-uri"] then
-				protocol, domain = response["digest-uri"]:match("(%w+)/(.*)$");
-				if protocol == nil or domain == nil then return "failure", "malformed-request" end
-			else
-				return "failure", "malformed-request", "Missing entry for digest-uri in SASL message."
-			end
+-- create a new SASL object which can be used to authenticate clients
+function new(realm, profile, forbidden)
+	local sasl_i = {profile = profile};
+	sasl_i.realm = realm;
+	local s = setmetatable(sasl_i, method);
+	if forbidden == nil then forbidden = {} end
+	s:forbidden(forbidden)
+	return s;
+end
+
+-- get a fresh clone with the same realm, profiles and forbidden mechanisms
+function method:clean_clone()
+	return new(self.realm, self.profile, self:forbidden())
+end
 
-			--TODO maybe realm support
-			self.username = response["username"];
-			local password_encoding, Y = self.credentials_handler("DIGEST-MD5", response["username"], self.realm, response["realm"], decoder);
-			if Y == nil then return "failure", "not-authorized"
-			elseif Y == false then return "failure", "account-disabled" end
-			local A1 = "";
-			if response.authzid then
-				if response.authzid == self.username or response.authzid == self.username.."@"..self.realm then
-					-- COMPAT
-					log("warn", "Client is violating RFC 3920 (section 6.1, point 7).");
-					A1 = Y..":"..response["nonce"]..":"..response["cnonce"]..":"..response.authzid;
-				else
-					return "failure", "invalid-authzid";
+-- set the forbidden mechanisms
+function method:forbidden( restrict )
+	if restrict then
+		-- set forbidden
+		self.restrict = set.new(restrict);
+	else
+		-- get forbidden
+		return array.collect(self.restrict:items());
+	end
+end
+
+-- get a list of possible SASL mechanims to use
+function method:mechanisms()
+	local mechanisms = {}
+	for backend, f in pairs(self.profile) do
+		if backend_mechanism[backend] then
+			for _, mechanism in ipairs(backend_mechanism[backend]) do
+				if not self.restrict:contains(mechanism) then
+					mechanisms[mechanism] = true;
 				end
-			else
-				A1 = Y..":"..response["nonce"]..":"..response["cnonce"];
 			end
-			local A2 = "AUTHENTICATE:"..protocol.."/"..domain;
-
-			local HA1 = md5(A1, true);
-			local HA2 = md5(A2, true);
-
-			local KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2;
-			local response_value = md5(KD, true);
-
-			if response_value == response["response"] then
-				-- calculate rspauth
-				A2 = ":"..protocol.."/"..domain;
-
-				HA1 = md5(A1, true);
-				HA2 = md5(A2, true);
-
-				KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2
-				local rspauth = md5(KD, true);
-				self.authenticated = true;
-				return "challenge", serialize({rspauth = rspauth});
-			else
-				return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated."
-			end
-		elseif self.step == 3 then
-			if self.authenticated ~= nil then return "success"
-			else return "failure", "malformed-request" end
 		end
 	end
-	return object;
+	self["possible_mechanisms"] = mechanisms;
+	return array.collect(keys(mechanisms));
 end
 
--- Credentials handler: Can be nil. If specified, should take the mechanism as
--- the only argument, and return true for OK, or false for not-OK (TODO)
-local function new_anonymous(realm, credentials_handler)
-	local object = { mechanism = "ANONYMOUS", realm = realm, credentials_handler = credentials_handler}
-		function object.feed(self, message)
-			return "success"
-		end
-	object["username"] = generate_uuid()
-	return object
+-- select a mechanism to use
+function method:select(mechanism)
+	if self.mech_i then
+		return false;
+	end
+	
+	self.mech_i = mechanisms[mechanism]
+	if self.mech_i == nil then 
+		return false;
+	end
+	return true;
 end
 
+-- feed new messages to process into the library
+function method:process(message)
+	--if message == "" or message == nil then return "failure", "malformed-request" end
+	return self.mech_i(self, message);
+end
 
-function new(mechanism, realm, credentials_handler)
-	local object
-	if mechanism == "PLAIN" then object = new_plain(realm, credentials_handler)
-	elseif mechanism == "DIGEST-MD5" then object = new_digest_md5(realm, credentials_handler)
-	elseif mechanism == "ANONYMOUS" then object = new_anonymous(realm, credentials_handler)
-	else
-		log("debug", "Unsupported SASL mechanism: "..tostring(mechanism));
-		return nil
-	end
-	return object
+-- load the mechanisms
+load_mechs = {"plain", "digest-md5", "anonymous", "scram"}
+for _, mech in ipairs(load_mechs) do
+	local name = "util.sasl."..mech;
+	local m = require(name);
+	m.init(registerMechanism)
 end
 
 return _M;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/sasl/anonymous.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -0,0 +1,36 @@
+-- sasl.lua v0.4
+-- Copyright (C) 2008-2009 Tobias Markmann
+--
+--    All rights reserved.
+--
+--    Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+--
+--        * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+--        * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+--        * Neither the name of Tobias Markmann nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+--
+--    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+local s_match = string.match;
+
+local log = require "util.logger".init("sasl");
+local generate_uuid = require "util.uuid".generate;
+
+module "anonymous"
+
+--=========================
+--SASL ANONYMOUS according to RFC 4505
+local function anonymous(self, message)
+	local username;
+	repeat
+		username = generate_uuid();
+	until self.profile.anonymous(username, self.realm);
+	self["username"] = username;
+	return "success"
+end
+
+function init(registerMechanism)
+	registerMechanism("ANONYMOUS", {"anonymous"}, anonymous);
+end
+
+return _M;
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/sasl/digest-md5.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -0,0 +1,229 @@
+-- sasl.lua v0.4
+-- Copyright (C) 2008-2009 Tobias Markmann
+--
+--    All rights reserved.
+--
+--    Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+--
+--        * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+--        * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+--        * Neither the name of Tobias Markmann nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+--
+--    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+local tostring = tostring;
+local type = type;
+
+local s_gmatch = string.gmatch;
+local s_match = string.match;
+local t_concat = table.concat;
+local t_insert = table.insert;
+local to_byte, to_char = string.byte, string.char;
+
+local md5 = require "util.hashes".md5;
+local log = require "util.logger".init("sasl");
+local generate_uuid = require "util.uuid".generate;
+
+module "digest-md5"
+
+--=========================
+--SASL DIGEST-MD5 according to RFC 2831
+
+local function digest(self, message)
+	--TODO complete support for authzid
+
+	local function serialize(message)
+		local data = ""
+
+		if type(message) ~= "table" then error("serialize needs an argument of type table.") end
+
+		-- testing all possible values
+		if message["realm"] then data = data..[[realm="]]..message.realm..[[",]] end
+		if message["nonce"] then data = data..[[nonce="]]..message.nonce..[[",]] end
+		if message["qop"] then data = data..[[qop="]]..message.qop..[[",]] end
+		if message["charset"] then data = data..[[charset=]]..message.charset.."," end
+		if message["algorithm"] then data = data..[[algorithm=]]..message.algorithm.."," end
+		if message["rspauth"] then data = data..[[rspauth=]]..message.rspauth.."," end
+		data = data:gsub(",$", "")
+		return data
+	end
+
+	local function utf8tolatin1ifpossible(passwd)
+		local i = 1;
+		while i <= #passwd do
+			local passwd_i = to_byte(passwd:sub(i, i));
+			if passwd_i > 0x7F then
+				if passwd_i < 0xC0 or passwd_i > 0xC3 then
+					return passwd;
+				end
+				i = i + 1;
+				passwd_i = to_byte(passwd:sub(i, i));
+				if passwd_i < 0x80 or passwd_i > 0xBF then
+					return passwd;
+				end
+			end
+			i = i + 1;
+		end
+
+		local p = {};
+		local j = 0;
+		i = 1;
+		while (i <= #passwd) do
+			local passwd_i = to_byte(passwd:sub(i, i));
+			if passwd_i > 0x7F then
+				i = i + 1;
+				local passwd_i_1 = to_byte(passwd:sub(i, i));
+				t_insert(p, to_char(passwd_i%4*64 + passwd_i_1%64)); -- I'm so clever
+			else
+				t_insert(p, to_char(passwd_i));
+			end
+			i = i + 1;
+		end
+		return t_concat(p);
+	end
+	local function latin1toutf8(str)
+		local p = {};
+		for ch in s_gmatch(str, ".") do
+			ch = to_byte(ch);
+			if (ch < 0x80) then
+				t_insert(p, to_char(ch));
+			elseif (ch < 0xC0) then
+				t_insert(p, to_char(0xC2, ch));
+			else
+				t_insert(p, to_char(0xC3, ch - 64));
+			end
+		end
+		return t_concat(p);
+	end
+	local function parse(data)
+		local message = {}
+		-- COMPAT: %z in the pattern to work around jwchat bug (sends "charset=utf-8\0")
+		for k, v in s_gmatch(data, [[([%w%-]+)="?([^",%z]*)"?,?]]) do -- FIXME The hacky regex makes me shudder
+			message[k] = v;
+		end
+		return message;
+	end
+
+	if not self.nonce then
+		self.nonce = generate_uuid();
+		self.step = 0;
+		self.nonce_count = {};
+	end
+
+	self.step = self.step + 1;
+	if (self.step == 1) then
+		local challenge = serialize({	nonce = self.nonce,
+										qop = "auth",
+										charset = "utf-8",
+										algorithm = "md5-sess",
+										realm = self.realm});
+		return "challenge", challenge;
+	elseif (self.step == 2) then
+		local response = parse(message);
+		-- check for replay attack
+		if response["nc"] then
+			if self.nonce_count[response["nc"]] then return "failure", "not-authorized" end
+		end
+
+		-- check for username, it's REQUIRED by RFC 2831
+		if not response["username"] then
+			return "failure", "malformed-request";
+		end
+		self["username"] = response["username"];
+
+		-- check for nonce, ...
+		if not response["nonce"] then
+			return "failure", "malformed-request";
+		else
+			-- check if it's the right nonce
+			if response["nonce"] ~= tostring(self.nonce) then return "failure", "malformed-request" end
+		end
+
+		if not response["cnonce"] then return "failure", "malformed-request", "Missing entry for cnonce in SASL message." end
+		if not response["qop"] then response["qop"] = "auth" end
+
+		if response["realm"] == nil or response["realm"] == "" then
+			response["realm"] = "";
+		elseif response["realm"] ~= self.realm then
+			return "failure", "not-authorized", "Incorrect realm value";
+		end
+
+		local decoder;
+		if response["charset"] == nil then
+			decoder = utf8tolatin1ifpossible;
+		elseif response["charset"] ~= "utf-8" then
+			return "failure", "incorrect-encoding", "The client's response uses "..response["charset"].." for encoding with isn't supported by sasl.lua. Supported encodings are latin or utf-8.";
+		end
+
+		local domain = "";
+		local protocol = "";
+		if response["digest-uri"] then
+			protocol, domain = response["digest-uri"]:match("(%w+)/(.*)$");
+			if protocol == nil or domain == nil then return "failure", "malformed-request" end
+		else
+			return "failure", "malformed-request", "Missing entry for digest-uri in SASL message."
+		end
+
+		--TODO maybe realm support
+		self.username = response["username"];
+		local Y, state;
+		if self.profile.plain then
+			local password, state = self.profile.plain(response["username"], self.realm)
+			if state == nil then return "failure", "not-authorized"
+			elseif state == false then return "failure", "account-disabled" end
+			Y = md5(response["username"]..":"..response["realm"]..":"..password);
+		elseif self.profile["digest-md5"] then
+			Y, state = self.profile["digest-md5"](response["username"], self.realm, response["realm"], response["charset"])
+			if state == nil then return "failure", "not-authorized"
+			elseif state == false then return "failure", "account-disabled" end
+		elseif self.profile["digest-md5-test"] then
+			-- TODO
+		end
+		--local password_encoding, Y = self.credentials_handler("DIGEST-MD5", response["username"], self.realm, response["realm"], decoder);
+		--if Y == nil then return "failure", "not-authorized"
+		--elseif Y == false then return "failure", "account-disabled" end
+		local A1 = "";
+		if response.authzid then
+			if response.authzid == self.username or response.authzid == self.username.."@"..self.realm then
+				-- COMPAT
+				log("warn", "Client is violating RFC 3920 (section 6.1, point 7).");
+				A1 = Y..":"..response["nonce"]..":"..response["cnonce"]..":"..response.authzid;
+			else
+				return "failure", "invalid-authzid";
+			end
+		else
+			A1 = Y..":"..response["nonce"]..":"..response["cnonce"];
+		end
+		local A2 = "AUTHENTICATE:"..protocol.."/"..domain;
+
+		local HA1 = md5(A1, true);
+		local HA2 = md5(A2, true);
+
+		local KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2;
+		local response_value = md5(KD, true);
+
+		if response_value == response["response"] then
+			-- calculate rspauth
+			A2 = ":"..protocol.."/"..domain;
+
+			HA1 = md5(A1, true);
+			HA2 = md5(A2, true);
+
+			KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2
+			local rspauth = md5(KD, true);
+			self.authenticated = true;
+			return "success", serialize({rspauth = rspauth});
+		else
+			return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated."
+		end
+	elseif self.step == 3 then
+		if self.authenticated ~= nil then return "success"
+		else return "failure", "malformed-request" end
+	end
+end
+
+function init(registerMechanism)
+	registerMechanism("DIGEST-MD5", {"plain"}, digest);
+end
+
+return _M;
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/sasl/plain.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -0,0 +1,67 @@
+-- sasl.lua v0.4
+-- Copyright (C) 2008-2009 Tobias Markmann
+--
+--    All rights reserved.
+--
+--    Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+--
+--        * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+--        * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+--        * Neither the name of Tobias Markmann nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+--
+--    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+local s_match = string.match;
+local saslprep = require "util.encodings".stringprep.saslprep;
+local log = require "util.logger".init("sasl");
+
+module "plain"
+
+-- ================================
+-- SASL PLAIN according to RFC 4616
+local function plain(self, message)
+	if not message then
+		return "failure", "malformed-request";
+	end
+
+	local authorization, authentication, password = s_match(message, "^([^%z]+)%z([^%z]+)%z([^%z]+)");
+
+	if not authorization then
+		return "failure", "malformed-request";
+	end
+
+	-- SASLprep password and authentication
+	authentication = saslprep(authentication);
+	password = saslprep(password);
+
+	if (not password) or (password == "") or (not authentication) or (authentication == "") then
+		log("debug", "Username or password violates SASLprep.");
+		return "failure", "malformed-request", "Invalid username or password.";
+	end
+
+	local correct, state = false, false;
+	if self.profile.plain then
+		local correct_password;
+		correct_password, state = self.profile.plain(authentication, self.realm);
+		if correct_password == password then correct = true; else correct = false; end
+	elseif self.profile.plain_test then
+		correct, state = self.profile.plain_test(authentication, self.realm, password);
+	end
+
+	self.username = authentication
+	if not state then
+		return "failure", "account-disabled";
+	end
+
+	if correct then
+		return "success";
+	else
+		return "failure", "not-authorized", "Unable to authorize you with the authentication credentials you've sent.";
+	end
+end
+
+function init(registerMechanism)
+	registerMechanism("PLAIN", {"plain", "plain_test"}, plain);
+end
+
+return _M;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/sasl/scram.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -0,0 +1,154 @@
+-- sasl.lua v0.4
+-- Copyright (C) 2008-2009 Tobias Markmann
+--
+--    All rights reserved.
+--
+--    Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+--
+--        * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+--        * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+--        * Neither the name of Tobias Markmann nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+--
+--    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+local s_match = string.match;
+local type = type
+local string = string
+local base64 = require "util.encodings".base64;
+local xor = require "bit".bxor
+local hmac_sha1 = require "util.hmac".sha1;
+local sha1 = require "util.hashes".sha1;
+local generate_uuid = require "util.uuid".generate;
+local saslprep = require "util.encodings".stringprep.saslprep;
+local log = require "util.logger".init("sasl");
+
+module "scram"
+
+--=========================
+--SASL SCRAM-SHA-1 according to draft-ietf-sasl-scram-10
+local default_i = 4096
+
+local function bp( b )
+	local result = ""
+	for i=1, b:len() do
+		result = result.."\\"..b:byte(i)
+	end
+	return result
+end
+
+local function binaryXOR( a, b )
+	if a:len() > b:len() then
+		b = string.rep("\0", a:len() - b:len())..b
+	elseif string.len(a) < string.len(b) then
+		a = string.rep("\0", b:len() - a:len())..a
+	end
+	local result = ""
+	for i=1, a:len() do
+		result = result..string.char(xor(a:byte(i), b:byte(i)))
+	end
+	return result
+end
+
+-- hash algorithm independent Hi(PBKDF2) implementation
+local function Hi(hmac, str, salt, i)
+	local Ust = hmac(str, salt.."\0\0\0\1");
+	local res = Ust;	
+	for n=1,i-1 do
+		local Und = hmac(str, Ust)
+		res = binaryXOR(res, Und)
+		Ust = Und
+	end
+	return res
+end
+
+local function validate_username(username)
+	-- check for forbidden char sequences
+	for eq in username:gmatch("=(.?.?)") do
+		if eq ~= "2D" and eq ~= "3D" then
+			return false 
+		end 
+	end
+	
+	-- replace =2D with , and =3D with =
+	username:gsub("=2D", ",");
+	username:gsub("=3D", "=");
+	
+	-- apply SASLprep
+	username = saslprep(username);
+	return username;
+end
+
+local function scram_sha_1(self, message)
+	if not self.state then self["state"] = {} end
+	
+	if not self.state.name then
+		-- we are processing client_first_message
+		local client_first_message = message;
+		self.state["client_first_message"] = client_first_message;
+		self.state["name"] = client_first_message:match("n=(.+),r=")
+		self.state["clientnonce"] = client_first_message:match("r=([^,]+)")
+		
+		if not self.state.name or not self.state.clientnonce then
+			return "failure", "malformed-request";
+		end
+		
+		self.state.name = validate_username(self.state.name);
+		if not self.state.name then
+			log("debug", "Username violates either SASLprep or contains forbidden character sequences.")
+			return "failure", "malformed-request", "Invalid username.";
+		end
+		
+		self.state["servernonce"] = generate_uuid();
+		self.state["salt"] = generate_uuid();
+		
+		local server_first_message = "r="..self.state.clientnonce..self.state.servernonce..",s="..base64.encode(self.state.salt)..",i="..default_i;
+		self.state["server_first_message"] = server_first_message;
+		return "challenge", server_first_message
+	else
+		if type(message) ~= "string" then return "failure", "malformed-request" end
+		-- we are processing client_final_message
+		local client_final_message = message;
+		
+		self.state["proof"] = client_final_message:match("p=(.+)");
+		self.state["nonce"] = client_final_message:match("r=(.+),p=");
+		self.state["channelbinding"] = client_final_message:match("c=(.+),r=");
+		if not self.state.proof or not self.state.nonce or not self.state.channelbinding then
+			return "failure", "malformed-request", "Missing an attribute(p, r or c) in SASL message.";
+		end
+		
+		local password;
+		if self.profile.plain then
+			local password, state = self.profile.plain(self.state.name, self.realm)
+			if state == nil then return "failure", "not-authorized"
+			elseif state == false then return "failure", "account-disabled" end
+			password = saslprep(password);
+			if not password then
+				log("debug", "Password violates SASLprep.");
+				return "failure", "not-authorized", "Invalid password."
+			end
+		end
+		
+		local SaltedPassword = Hi(hmac_sha1, password, self.state.salt, default_i)
+		local ClientKey = hmac_sha1(SaltedPassword, "Client Key")
+		local ServerKey = hmac_sha1(SaltedPassword, "Server Key")
+		local StoredKey = sha1(ClientKey)
+		local AuthMessage = "n=" .. s_match(self.state.client_first_message,"n=(.+)") .. "," .. self.state.server_first_message .. "," .. s_match(client_final_message, "(.+),p=.+")
+		local ClientSignature = hmac_sha1(StoredKey, AuthMessage)
+		local ClientProof     = binaryXOR(ClientKey, ClientSignature)
+		local ServerSignature = hmac_sha1(ServerKey, AuthMessage)
+		
+		if base64.encode(ClientProof) == self.state.proof then
+			local server_final_message = "v="..base64.encode(ServerSignature);
+			self["username"] = self.state.name;
+			return "success", server_final_message;
+		else
+			return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated.";
+		end
+	end
+end
+
+function init(registerMechanism)
+	registerMechanism("SCRAM-SHA-1", {"plain"}, scram_sha_1);
+end
+
+return _M;
\ No newline at end of file
--- a/util/serialization.lua	Sun Nov 29 21:32:39 2009 +0100
+++ b/util/serialization.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -13,6 +13,7 @@
 local t_concat = table.concat;
 local error = error;
 local pairs = pairs;
+local next = next;
 
 local debug_traceback = debug.traceback;
 local log = require "util.logger".init("serialization");
@@ -34,21 +35,25 @@
 	elseif type(o) == "string" then
 		func(t, (("%q"):format(o):gsub("\\\n", "\\n")));
 	elseif type(o) == "table" then
-		func(t, "{\n");
-		for k,v in pairs(o) do
-			func(t, indent(ind));
-			func(t, "[");
-			func(t, basicSerialize(k));
-			func(t, "] = ");
-			if ind == 0 then
-				_simplesave(v, 0, t, func);
-			else
-				_simplesave(v, ind+1, t, func);
+		if next(o) ~= nil then
+			func(t, "{\n");
+			for k,v in pairs(o) do
+				func(t, indent(ind));
+				func(t, "[");
+				func(t, basicSerialize(k));
+				func(t, "] = ");
+				if ind == 0 then
+					_simplesave(v, 0, t, func);
+				else
+					_simplesave(v, ind+1, t, func);
+				end
+				func(t, ";\n");
 			end
-			func(t, ",\n");
+			func(t, indent(ind-1));
+			func(t, "}");
+		else
+			func(t, "{}");
 		end
-		func(t, indent(ind-1));
-		func(t, "}");
 	elseif type(o) == "boolean" then
 		func(t, (o and "true" or "false"));
 	else
--- a/util/timer.lua	Sun Nov 29 21:32:39 2009 +0100
+++ b/util/timer.lua	Sun Nov 29 21:33:37 2009 +0100
@@ -8,6 +8,9 @@
 
 
 local ns_addtimer = require "net.server".addtimer;
+local event = require "net.server".event;
+local event_base = require "net.server".event_base;
+
 local get_time = os.time;
 local t_insert = table.insert;
 local t_remove = table.remove;
@@ -19,33 +22,51 @@
 
 module "timer"
 
-local function _add_task(delay, func)
-	local current_time = get_time();
-	delay = delay + current_time;
-	if delay >= current_time then
-		t_insert(new_data, {delay, func});
-	else func(); end
+local _add_task;
+if not event then
+	function _add_task(delay, func)
+		local current_time = get_time();
+		delay = delay + current_time;
+		if delay >= current_time then
+			t_insert(new_data, {delay, func});
+		else
+			func();
+		end
+	end
+
+	ns_addtimer(function()
+		local current_time = get_time();
+		if #new_data > 0 then
+			for _, d in pairs(new_data) do
+				t_insert(data, d);
+			end
+			new_data = {};
+		end
+		
+		for i, d in pairs(data) do
+			local t, func = d[1], d[2];
+			if t <= current_time then
+				data[i] = nil;
+				local r = func(current_time);
+				if type(r) == "number" then _add_task(r, func); end
+			end
+		end
+	end);
+else
+	local EVENT_LEAVE = (event.core and event.core.LEAVE) or -1;
+	function _add_task(delay, func)
+		event_base:addevent(nil, event.EV_TIMEOUT, function ()
+			local ret = func();
+			if ret then
+				_add_task(ret, func);
+			else
+				return EVENT_LEAVE;
+			end
+		end
+		, delay);
+	end
 end
 
 add_task = _add_task;
 
-ns_addtimer(function()
-	local current_time = get_time();
-	if #new_data > 0 then
-		for _, d in pairs(new_data) do
-			t_insert(data, d);
-		end
-		new_data = {};
-	end
-	
-	for i, d in pairs(data) do
-		local t, func = d[1], d[2];
-		if t <= current_time then
-			data[i] = nil;
-			local r = func(current_time);
-			if type(r) == "number" then _add_task(r, func); end
-		end
-	end
-end);
-
 return _M;

mercurial