Changeset

59:9e1541b72522

Merge from waqas
author Matthew Wild <mwild1@gmail.com>
date Sat, 04 Oct 2008 19:24:43 +0100
parents 57:126b25079399 (diff) 58:85a93adef6f6 (current diff)
children 61:7afc23a8114e
files
diffstat 12 files changed, 224 insertions(+), 45 deletions(-) [+]
line wrap: on
line diff
--- a/core/modulemanager.lua	Sat Oct 04 19:51:59 2008 +0500
+++ b/core/modulemanager.lua	Sat Oct 04 19:24:43 2008 +0100
@@ -4,6 +4,7 @@
 local loadfile, pcall = loadfile, pcall;
 local setmetatable, setfenv, getfenv = setmetatable, setfenv, getfenv;
 local pairs, ipairs = pairs, ipairs;
+local t_insert = table.insert;
 local type = type;
 
 local tostring, print = tostring, print;
@@ -18,6 +19,7 @@
 local modulehelpers = setmetatable({}, { __index = _G });
 
 function modulehelpers.add_iq_handler(origin_type, xmlns, handler)
+	if not (origin_type and handler and xmlns) then return false; end
 	handlers[origin_type] = handlers[origin_type] or {};
 	handlers[origin_type].iq = handlers[origin_type].iq or {};
 	if not handlers[origin_type].iq[xmlns] then
@@ -29,17 +31,19 @@
 	end
 end
 
-function modulehelpers.add_handler(origin_type, tag, handler)
+function modulehelpers.add_handler(origin_type, tag, xmlns, handler)
+	if not (origin_type and tag and xmlns and handler) then return false; end
 	handlers[origin_type] = handlers[origin_type] or {};
 	if not handlers[origin_type][tag] then
-		handlers[origin_type][tag]= handler;
+		handlers[origin_type][tag] = handlers[origin_type][tag] or {};
+		handlers[origin_type][tag][xmlns]= handler;
 		handler_info[handler] = getfenv(2).module;
 		log("debug", "mod_%s now handles tag '%s'", getfenv(2).module.name, tag);
 	elseif handler_info[handlers[origin_type][tag]] then
 		log("warning", "mod_%s wants to handle tag '%s' but mod_%s already handles that", getfenv(2).module.name, tag, handler_info[handlers[origin_type][tag]].module.name);
 	end
 end
-					
+
 function loadall()
 	load("saslauth");
 	load("legacyauth");
@@ -79,14 +83,38 @@
 			end
 
 		end
-		--FIXME: All iq's must be replied to, here we should return service-unavailable I think
 	elseif handlers[origin_type] then
 		local handler = handlers[origin_type][name];
 		if  handler then
-			log("debug", "Passing stanza to mod_%s", handler_info[handler].name);
-			return handler(origin, stanza) or true;
+			handler = handler[xmlns];
+			if handler then
+				log("debug", "Passing stanza to mod_%s", handler_info[handler].name);
+				return handler(origin, stanza) or true;
+			end
 		end
 	end
-	log("debug", "Stanza unhandled by any modules");
+	log("debug", "Stanza unhandled by any modules, xmlns: %s", stanza.attr.xmlns);
 	return false; -- we didn't handle it
 end
+
+do
+	local event_handlers = {};
+	
+	function modulehelpers.add_event_hook(name, handler)
+		if not event_handlers[name] then
+			event_handlers[name] = {};
+		end
+		t_insert(event_handlers[name] , handler);
+	end
+	
+	function fire_event(name, ...)
+		local event_handlers = event_handlers[name];
+		if event_handlers then
+			for name, handler in ipairs(event_handlers) do
+				handler(...);
+			end
+		end
+	end
+end
+
+return _M;
--- a/core/servermanager.lua	Sat Oct 04 19:51:59 2008 +0500
+++ b/core/servermanager.lua	Sat Oct 04 19:24:43 2008 +0100
@@ -1,8 +1,20 @@
+
+local st = require "util.stanza";
+local send = require "core.sessionmanager".send_to_session;
+local xmlns_stanzas ='urn:ietf:params:xml:ns:xmpp-stanzas';
 
 require "modulemanager"
 
 -- Handle stanzas that were addressed to the server (whether they came from c2s, s2s, etc.)
 function handle_stanza(origin, stanza)
 	-- Use plugins
-	return modulemanager.handle_stanza(origin, stanza);
+	if not modulemanager.handle_stanza(origin, stanza) then
+		if stanza.name == "iq" then
+			local reply = st.reply(stanza);
+			reply.attr.type = "error";
+			reply:tag("error", { type = "cancel" })
+				:tag("service-unavailable", { xmlns = xmlns_stanzas });
+			send(origin, reply);
+		end
+	end
 end
--- a/core/sessionmanager.lua	Sat Oct 04 19:51:59 2008 +0500
+++ b/core/sessionmanager.lua	Sat Oct 04 19:24:43 2008 +0100
@@ -1,22 +1,62 @@
 
-local tostring = tostring;
-
-local print = print;
+local tonumber, tostring = tonumber, tostring;
+local ipairs, pairs, print= ipairs, pairs, print;
+local collectgarbage = collectgarbage;
+local m_random = import("math", "random");
+local format = import("string", "format");
 
 local hosts = hosts;
+local sessions = sessions;
 
+local modulemanager = require "core.modulemanager";
 local log = require "util.logger".init("sessionmanager");
+local error = error;
+local uuid_generate = require "util.uuid".uuid_generate;
+
+local newproxy = newproxy;
+local getmetatable = getmetatable;
 
 module "sessionmanager"
 
 function new_session(conn)
 	local session = { conn = conn, notopen = true, priority = 0, type = "c2s_unauthed" };
+	if true then
+		session.trace = newproxy(true);
+		getmetatable(session.trace).__gc = function () print("Session got collected") end;
+	end
 	local w = conn.write;
 	session.send = function (t) w(tostring(t)); end
 	return session;
 end
 
 function destroy_session(session)
+	if not (session and session.disconnect) then return; end 
+	log("debug", "Destroying session...");
+	session.disconnect();
+	if session.username then
+		if session.resource then
+			hosts[session.host].sessions[session.username].sessions[session.resource] = nil;
+		end
+		local nomore = true;
+		for res, ssn in pairs(hosts[session.host].sessions[session.username]) do
+			nomore = false;
+		end
+		if nomore then
+			hosts[session.host].sessions[session.username] = nil;
+		end
+	end
+	session.conn = nil;
+	session.disconnect = nil;
+	for k in pairs(session) do
+		if k ~= "trace" then
+			session[k] = nil;
+		end
+	end
+	collectgarbage("collect");
+	collectgarbage("collect");
+	collectgarbage("collect");
+	collectgarbage("collect");
+	collectgarbage("collect");
 end
 
 function send_to_session(session, data)
@@ -26,16 +66,16 @@
 
 function make_authenticated(session, username)
 	session.username = username;
-	session.resource = resource;
 	if session.type == "c2s_unauthed" then
 		session.type = "c2s";
 	end
+	return true;
 end
 
 function bind_resource(session, resource)
 	if not session.username then return false, "auth"; end
 	if session.resource then return false, "constraint"; end -- We don't support binding multiple resources
-	resource = resource or math.random(100000, 99999999); -- FIXME: Clearly we have issues :)
+	resource = resource or uuid_generate();
 	--FIXME: Randomly-generated resources must be unique per-user, and never conflict with existing
 	
 	if not hosts[session.host].sessions[session.username] then
@@ -54,4 +94,27 @@
 	return true;
 end
 
+function streamopened(session, attr)
+						local send = session.send;
+						session.host = attr.to or error("Client failed to specify destination hostname");
+			                        session.version = tonumber(attr.version) or 0;
+			                        session.streamid = m_random(1000000, 99999999);
+			                        print(session, session.host, "Client opened stream");
+			                        send("<?xml version='1.0'?>");
+			                        send(format("<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='%s' from='%s' version='1.0'>", session.streamid, session.host));
+						
+						local features = {};
+						modulemanager.fire_event("stream-features", session, features);
+						
+						send("<stream:features>");
+						
+						for _, feature in ipairs(features) do
+							send_to_session(session, tostring(feature));
+						end
+ 
+        			                send("</stream:features>");
+						log("info", "Stream opened successfully");
+						session.notopen = nil;
+end
+
 return _M;
\ No newline at end of file
--- a/core/usermanager.lua	Sat Oct 04 19:51:59 2008 +0500
+++ b/core/usermanager.lua	Sat Oct 04 19:24:43 2008 +0100
@@ -1,10 +1,12 @@
 
 require "util.datamanager"
 local datamanager = datamanager;
+local log = require "util.logger".init("usermanager");
 
 module "usermanager"
 
 function validate_credentials(host, username, password)
+	log("debug", "User '%s' is being validated", username);
 	local credentials = datamanager.load(username, host, "accounts") or {};
 	if password == credentials.password then return true; end
 	return false;
--- a/core/xmlhandlers.lua	Sat Oct 04 19:51:59 2008 +0500
+++ b/core/xmlhandlers.lua	Sat Oct 04 19:24:43 2008 +0100
@@ -1,4 +1,5 @@
 
+local sessionmanager_streamopened = require "core.sessionmanager".streamopened;
 require "util.stanza"
 
 local st = stanza;
@@ -9,6 +10,7 @@
 local t_remove = table.remove;
 local t_concat = table.concat;
 local t_concatall = function (t, sep) local tt = {}; for _, s in ipairs(t) do t_insert(tt, tostring(s)); end return t_concat(tt, sep); end
+local sm_destroy_session = import("core.sessionmanager", "destroy_session");
 
 local error = error;
 
@@ -27,7 +29,6 @@
 		
 		local stanza
 		function xml_handlers:StartElement(name, attr)
-				log("info", "xmlhandlers", "Start element: " .. name);
 			if stanza and #chardata > 0 then
 				-- We have some character data in the buffer
 				stanza:text(t_concat(chardata));
@@ -37,24 +38,7 @@
 			if not stanza then
 				if session.notopen then
 					if name == "stream" then
-						session.host = attr.to or error("Client failed to specify destination hostname");
-			                        session.version = attr.version or 0;
-			                        session.streamid = m_random(1000000, 99999999);
-			                        print(session, session.host, "Client opened stream");
-			                        send("<?xml version='1.0'?>");
-			                        send(format("<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='%s' from='%s' version='1.0'>", session.streamid, session.host));
-						send("<stream:features>");
-						if not session.username then
-							send("<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>");
-								send("<mechanism>PLAIN</mechanism>");
-							send("</mechanisms>");
-						else
-							send("<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><required/></bind>");
-						end
-        			                --send [[<register xmlns="http://jabber.org/features/iq-register"/> ]]
-        			                send("</stream:features>");
-						log("info", "core", "Stream opened successfully");
-						session.notopen = nil;
+						sessionmanager_streamopened(session, attr);
 						return;
 					end
 					error("Client failed to open stream successfully");
@@ -77,7 +61,15 @@
 		end
 		function xml_handlers:EndElement(name)
 			curr_ns,name = name:match("^(.+):(%w+)$");
-			if (not stanza) or #stanza.last_add < 0 or (#stanza.last_add > 0 and name ~= stanza.last_add[#stanza.last_add].name) then error("XML parse error in client stream"); end
+			if (not stanza) or #stanza.last_add < 0 or (#stanza.last_add > 0 and name ~= stanza.last_add[#stanza.last_add].name) then 
+				if name == "stream" then
+					log("debug", "Stream closed");
+					sm_destroy_session(session);
+					return;
+				else
+					error("XML parse error in client stream");
+				end
+			end
 			if stanza and #chardata > 0 then
 				-- We have some character data in the buffer
 				stanza:text(t_concat(chardata));
--- a/main.lua	Sat Oct 04 19:51:59 2008 +0500
+++ b/main.lua	Sat Oct 04 19:24:43 2008 +0100
@@ -13,6 +13,7 @@
  
 sessions = {};
  
+require "util.import"
 require "core.stanza_dispatch"
 require "core.xmlhandlers"
 require "core.rostermanager"
@@ -24,6 +25,7 @@
 require "net.connhandlers"
 require "util.stanza"
 require "util.jid"
+
  
 -- Locals for faster access --
 local t_insert = table.insert;
@@ -31,6 +33,7 @@
 local t_concatall = function (t, sep) local tt = {}; for _, s in ipairs(t) do t_insert(tt, tostring(s)); end return t_concat(tt, sep); end
 local m_random = math.random;
 local format = string.format;
+local sm_new_session, sm_destroy_session = sessionmanager.new_session, sessionmanager.destroy_session; --import("core.sessionmanager", "new_session", "destroy_session");
 local st = stanza;
 ------------------------------
 
@@ -46,7 +49,7 @@
 	local session = sessions[conn];
 
 	if not session then
-		sessions[conn] = sessionmanager.new_session(conn);
+		sessions[conn] = sm_new_session(conn);
 		session = sessions[conn];
 
 		-- Logging functions --
@@ -73,11 +76,8 @@
 				pres:tag("status"):text("Disconnected: "..err);
 				session.stanza_dispatch(pres);
 			end
-			if session.username then
-				hosts[session.host].sessions[session.username] = nil;
-			end
 			session = nil;
-			print("Disconnected: "..err);
+			print("Disconnected: "..tostring(err));
 			collectgarbage("collect");
 		end
 	end
@@ -89,7 +89,7 @@
 end
 
 function disconnect(conn, err)
-	sessions[conn].disconnect(err);
+	sm_destroy_session(sessions[conn]);
 	sessions[conn] = nil;
 end
 
--- a/plugins/mod_legacyauth.lua	Sat Oct 04 19:51:59 2008 +0500
+++ b/plugins/mod_legacyauth.lua	Sat Oct 04 19:24:43 2008 +0100
@@ -38,7 +38,7 @@
 								reply:tag("error", { code = "401", type = "auth" })
 									:tag("not-authorized", { xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas" });
 							end
-							dispatch_stanza(reply);
+							send(session, reply);
 							return true;
 						end
 					end
--- a/plugins/mod_saslauth.lua	Sat Oct 04 19:51:59 2008 +0500
+++ b/plugins/mod_saslauth.lua	Sat Oct 04 19:24:43 2008 +0100
@@ -1,19 +1,22 @@
 
 local st = require "util.stanza";
 local send = require "core.sessionmanager".send_to_session;
+local sm_bind_resource = require "core.sessionmanager".bind_resource;
 
 local usermanager_validate_credentials = require "core.usermanager".validate_credentials;
-local t_concat = table.concat;
+local t_concat, t_insert = table.concat, table.insert;
 local tostring = tostring;
 
 local log = require "util.logger".init("mod_saslauth");
 
 local xmlns_sasl ='urn:ietf:params:xml:ns:xmpp-sasl';
+local xmlns_bind ='urn:ietf:params:xml:ns:xmpp-bind';
+local xmlns_stanzas ='urn:ietf:params:xml:ns:xmpp-stanzas';
 
 local new_connhandler = require "net.connhandlers".new;
 local new_sasl = require "util.sasl".new;
 
-add_handler("c2s_unauthed", "auth",
+add_handler("c2s_unauthed", "auth", xmlns_sasl,
 		function (session, stanza)
 			if not session.sasl_handler then
 				session.sasl_handler = new_sasl(stanza.attr.mechanism, 
@@ -30,6 +33,7 @@
 						local success, err = sessionmanager.make_authenticated(session, username);
 						if not success then
 							sessionmanager.destroy_session(session);
+							return;
 						end
 						session.sasl_handler = nil;
 						session.connhandler = new_connhandler("xmpp-client", session);
@@ -50,4 +54,60 @@
 				error("Client tried to negotiate SASL again", 0);
 			end
 			
-		end);
\ No newline at end of file
+		end);
+		
+add_event_hook("stream-features", 
+					function (session, features)												
+						if not session.username then
+							t_insert(features, "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>");
+								t_insert(features, "<mechanism>PLAIN</mechanism>");
+							t_insert(features, "</mechanisms>");
+						else
+							t_insert(features, "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><required/></bind>");
+							t_insert(features, "<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>");
+						end
+						--send [[<register xmlns="http://jabber.org/features/iq-register"/> ]]
+					end);
+					
+add_iq_handler("c2s", "urn:ietf:params:xml:ns:xmpp-bind", 
+		function (session, stanza)
+			log("debug", "Client tried to bind to a resource");
+			local resource;
+			if stanza.attr.type == "set" then
+				local bind = stanza.tags[1];
+				
+				if bind and bind.attr.xmlns == xmlns_bind then
+					resource = bind:child_with_name("resource");
+					if resource then
+						resource = resource[1];
+					end
+				end
+			end
+			local success, err = sm_bind_resource(session, resource);
+			if not success then
+				local reply = st.reply(stanza);
+				reply.attr.type = "error";
+				if err == "conflict" then
+					reply:tag("error", { type = "modify" })
+						:tag("conflict", { xmlns = xmlns_stanzas });
+				elseif err == "constraint" then
+					reply:tag("error", { type = "cancel" })
+						:tag("resource-constraint", { xmlns = xmlns_stanzas });
+				elseif err == "auth" then
+					reply:tag("error", { type = "cancel" })
+						:tag("not-allowed", { xmlns = xmlns_stanzas });
+				end
+				send(session, reply);
+			else
+				local reply = st.reply(stanza);
+				reply:tag("bind", { xmlns = xmlns_bind})
+					:tag("jid"):text(session.full_jid);
+				send(session, reply);
+			end
+		end);
+		
+add_iq_handler("c2s", "urn:ietf:params:xml:ns:xmpp-session", 
+		function (session, stanza)
+			log("debug", "Client tried to bind to a resource");
+			send(session, st.reply(stanza));
+		end);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/import.lua	Sat Oct 04 19:24:43 2008 +0100
@@ -0,0 +1,13 @@
+
+local t_insert = table.insert;
+function import(module, ...)
+	local m = package.loaded[module] or require(module);
+	if type(m) == "table" and ... then
+		local ret = {};
+		for _, f in ipairs{...} do
+			t_insert(ret, m[f]);
+		end
+		return unpack(ret);
+	end
+	return m;
+end
--- a/util/logger.lua	Sat Oct 04 19:51:59 2008 +0500
+++ b/util/logger.lua	Sat Oct 04 19:24:43 2008 +0100
@@ -9,7 +9,7 @@
 	name = nil; -- While this line is not commented, will automatically fill in file/line number info
 	return 	function (level, message, ...)
 				if not name then
-					local inf = debug.getinfo(2, 'Snl');
+					local inf = debug.getinfo(3, 'Snl');
 					level = level .. ","..tostring(inf.short_src):match("[^/]*$")..":"..inf.currentline;
 				end
 				if ... then 
--- a/util/sasl.lua	Sat Oct 04 19:51:59 2008 +0500
+++ b/util/sasl.lua	Sat Oct 04 19:24:43 2008 +0100
@@ -19,7 +19,7 @@
 						local authorization = s_match(response, "([^&%z]+)")
 						local authentication = s_match(response, "%z([^&%z]+)%z")
 						local password = s_match(response, "%z[^&%z]+%z([^&%z]+)")
-						if self.onAuth(authorization, password) == true then
+						if self.onAuth(authentication, password) == true then
 							self.onWrite(st.stanza("success", {xmlns = "urn:ietf:params:xml:ns:xmpp-sasl"}))
 							self.onSuccess(authentication)
 						else
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/uuid.lua	Sat Oct 04 19:24:43 2008 +0100
@@ -0,0 +1,9 @@
+
+local m_random = math.random;
+module "uuid"
+
+function uuid_generate()
+	return m_random(0, 99999999);
+end
+
+return _M;
\ No newline at end of file