main.lua
changeset 0 3e3171b59028
child 1 b8787e859fd2
equal deleted inserted replaced
-1:000000000000 0:3e3171b59028
       
     1 require "luarocks.require"
       
     2 
       
     3 require "copas"
       
     4 require "socket"
       
     5 require "ssl"
       
     6 require "lxp"
       
     7 
       
     8 function log(type, area, message)
       
     9 	print(type, area, message);
       
    10 end
       
    11 
       
    12 require "core.stanza_dispatch"
       
    13 require "core.rostermanager"
       
    14 require "core.offlinemessage"
       
    15 require "util.stanza"
       
    16 require "util.jid"
       
    17 
       
    18 -- Locals for faster access --
       
    19 local t_insert = table.insert;
       
    20 local t_concat = table.concat;
       
    21 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
       
    22 local m_random = math.random;
       
    23 local format = string.format;
       
    24 local st = stanza;
       
    25 ------------------------------
       
    26 
       
    27 users = {};
       
    28 hosts = 	{ 
       
    29 			["localhost"] = 	{
       
    30 							type = "local";
       
    31 							connected = true;
       
    32 							sessions = {};
       
    33 						};
       
    34 			["getjabber.ath.cx"] = 	{
       
    35 							type = "local";
       
    36 							connected = true;
       
    37 							sessions = {};
       
    38 						};
       
    39 		}
       
    40 
       
    41 local hosts, users = hosts, users;
       
    42 
       
    43 local ssl_ctx, msg = ssl.newcontext { mode = "server", protocol = "sslv23", key = "/home/matthew/ssl_cert/server.key",
       
    44     certificate = "/home/matthew/ssl_cert/server.crt", capath = "/etc/ssl/certs", verify = "none", }
       
    45         
       
    46 if not ssl_ctx then error("Failed to initialise SSL/TLS support: "..tostring(msg)); end
       
    47 
       
    48 
       
    49 function connect_host(host)
       
    50 	hosts[host] = { type = "remote", sendbuffer = {} };
       
    51 end
       
    52 
       
    53 function handler(conn)
       
    54 	local copas_receive, copas_send = copas.receive, copas.send;
       
    55 	local reqdata, sktmsg;
       
    56 	local session = { sendbuffer = { external = {} }, conn = conn, notopen = true, priority = 0 }
       
    57 
       
    58 
       
    59 	-- Logging functions --
       
    60 
       
    61 	local mainlog, log = log;
       
    62 	do
       
    63 		local conn_name = tostring(conn):match("%w+$");
       
    64 		log = function (type, area, message) mainlog(type, conn_name, message); end
       
    65 	end
       
    66 	local print = function (...) log("info", "core", t_concatall({...}, "\t")); end
       
    67 	session.log = log;
       
    68 
       
    69 	--	--	--
       
    70 
       
    71 	-- Send buffers --
       
    72 
       
    73 	local sendbuffer = session.sendbuffer;
       
    74 	local send = function (data) return t_insert(sendbuffer, tostring(data)); end;
       
    75 	local send_to = 	function (to, stanza)
       
    76 					local node, host, resource = jid.split(to);
       
    77 					print("Routing stanza to "..to..":", node, host, resource);
       
    78 					if not hosts[host] then
       
    79 						print("   ...but host offline, establishing connection");
       
    80 						connect_host(host);
       
    81 						t_insert(hosts[host].sendbuffer, stanza); -- This will be sent when s2s connection succeeds					
       
    82 					elseif hosts[host].connected then
       
    83 						print("   ...putting in our external send buffer");
       
    84 						t_insert(sendbuffer.external, { node = node, host = host, resource = resource, data = stanza});
       
    85 						print("   ...there are now "..tostring(#sendbuffer.external).." stanzas in the external send buffer");
       
    86 					end
       
    87 				end
       
    88 	session.send, session.send_to = send, send_to;
       
    89 
       
    90 	--	--	--
       
    91 	print("Client connected");
       
    92 	conn = ssl.wrap(copas.wrap(conn), ssl_ctx);
       
    93 	
       
    94 	do
       
    95 		local succ, msg
       
    96 		conn:settimeout(15)
       
    97 		while not succ do
       
    98 			succ, msg = conn:dohandshake()
       
    99 			if not succ then
       
   100 				print("SSL: "..tostring(msg));
       
   101 				if msg == 'wantread' then
       
   102 					socket.select({conn}, nil)
       
   103 				elseif msg == 'wantwrite' then
       
   104 					socket.select(nil, {conn})
       
   105 				else
       
   106 					-- other error
       
   107 				end
       
   108 			end
       
   109 		end
       
   110 	end
       
   111 	print("SSL handshake complete");
       
   112 	-- XML parser initialisation --
       
   113 
       
   114 	local parser;
       
   115 	local stanza;
       
   116 	
       
   117 	local stanza_dispatch = init_stanza_dispatcher(session);
       
   118 
       
   119 	local xml_handlers = {};
       
   120 	
       
   121 	do
       
   122 		local ns_stack = { "" };
       
   123 		local curr_ns = "";
       
   124 		local curr_tag;
       
   125 		function xml_handlers:StartElement(name, attr)
       
   126 			curr_ns,name = name:match("^(.+):(%w+)$");
       
   127 			print("Tag received:", name, tostring(curr_ns));
       
   128 			if not stanza then
       
   129 				if session.notopen then
       
   130 					if name == "stream" then
       
   131 						session.host = attr.to or error("Client failed to specify destination hostname");
       
   132 			                        session.version = attr.version or 0;
       
   133 			                        session.streamid = m_random(1000000, 99999999);
       
   134 			                        print(session, session.host, "Client opened stream");
       
   135 			                        send("<?xml version='1.0'?>");
       
   136 			                        send(format("<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='%s' from='%s' >", session.streamid, session.host));
       
   137 			                        --send("<stream:features>");
       
   138 			                        --send("<mechanism>PLAIN</mechanism>");
       
   139         			                --send [[<register xmlns="http://jabber.org/features/iq-register"/> ]]
       
   140         			                --send("</stream:features>");
       
   141 						log("info", "core", "Stream opened successfully");
       
   142 						session.notopen = nil;
       
   143 						return;
       
   144 					end
       
   145 					error("Client failed to open stream successfully");
       
   146 				end
       
   147 				if name ~= "iq" and name ~= "presence" and name ~= "message" then
       
   148 					error("Client sent invalid top-level stanza");
       
   149 				end
       
   150 				stanza = st.stanza(name, { to = attr.to, type = attr.type, id = attr.id, xmlns = curr_ns });
       
   151 				curr_tag = stanza;
       
   152 			else
       
   153 				attr.xmlns = curr_ns;
       
   154 				stanza:tag(name, attr);
       
   155 			end
       
   156 		end
       
   157 		function xml_handlers:CharacterData(data)
       
   158 			if data:match("%S") then
       
   159 				stanza:text(data);
       
   160 			end
       
   161 		end
       
   162 		function xml_handlers:EndElement(name)
       
   163 			curr_ns,name = name:match("^(.+):(%w+)$");
       
   164 			--print("<"..name.."/>", tostring(stanza), tostring(#stanza.last_add < 1), tostring(stanza.last_add[#stanza.last_add].name));
       
   165 			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
       
   166 			-- Complete stanza
       
   167 			print(name, tostring(#stanza.last_add));
       
   168 			if #stanza.last_add == 0 then
       
   169 				stanza_dispatch(stanza);
       
   170 				stanza = nil;
       
   171 			else
       
   172 				stanza:up();
       
   173 			end
       
   174 		end
       
   175 --[[		function xml_handlers:StartNamespaceDecl(namespace)
       
   176 			table.insert(ns_stack, namespace);
       
   177 			curr_ns = namespace;
       
   178 			log("debug", "parser", "Entering namespace "..tostring(curr_ns));
       
   179 		end
       
   180 		function xml_handlers:EndNamespaceDecl(namespace)
       
   181 			table.remove(ns_stack);
       
   182 			log("debug", "parser", "Leaving namespace "..tostring(curr_ns));
       
   183 			curr_ns = ns_stack[#ns_stack];
       
   184 			log("debug", "parser", "Entering namespace "..tostring(curr_ns));
       
   185 		end
       
   186 ]]
       
   187 	end
       
   188 	parser = lxp.new(xml_handlers, ":");
       
   189 
       
   190 	--	--	--
       
   191 
       
   192 	-- Main loop --
       
   193 	print "Receiving..."
       
   194 	reqdata = copas_receive(conn, 1);
       
   195 	print "Received"
       
   196 	while reqdata do
       
   197 		parser:parse(reqdata);
       
   198 		if #sendbuffer.external > 0 then
       
   199 			-- Stanzas queued to go to other places, from us
       
   200 			-- ie. other local users, or remote hosts that weren't connected before
       
   201 			print(#sendbuffer.external.." stanzas queued for other recipients, sending now...");
       
   202 			for n, packet in pairs(sendbuffer.external) do
       
   203 				if not hosts[packet.host] then
       
   204 					connect_host(packet.host);
       
   205 					t_insert(hosts[packet.host].sendbuffer, packet.data);
       
   206 				elseif hosts[packet.host].type == "local" then
       
   207 					print("   ...is to a local user")
       
   208 					local destuser = hosts[packet.host].sessions[packet.node];
       
   209 					if destuser and destuser.sessions then
       
   210 						if not destuser.sessions[packet.resource] then
       
   211 							local best_resource;
       
   212 							for resource, session in pairs(destuser.sessions) do
       
   213 								if not best_session then best_session = session;
       
   214 								elseif session.priority >= best_session.priority and session.priority >= 0 then
       
   215 									best_session = session;
       
   216 								end
       
   217 							end
       
   218 							if not best_session then
       
   219 								offlinemessage.new(packet.node, packet.host, packet.data);
       
   220 							else
       
   221 								print("resource '"..packet.resource.."' was not online, have chosen to send to '"..best_session.username.."@"..best_session.host.."/"..best_session.resource.."'");
       
   222 								packet.resource = best_session.resource;
       
   223 							end
       
   224 						end
       
   225 						if destuser.sessions[packet.resource] == session then
       
   226 							log("warn", "core", "Attempt to send stanza to self, dropping...");
       
   227 						else
       
   228 							print("...sending...");
       
   229 							copas_send(destuser.sessions[packet.resource].conn, tostring(packet.data));
       
   230 							print("...sent")
       
   231 						end
       
   232 					elseif packet.data.name == "message" then
       
   233 						print("   ...will be stored offline");
       
   234 						offlinemessage.new(packet.node, packet.host, packet.data);
       
   235 					elseif packet.data.name == "iq" then
       
   236 						print("   ...is an iq");
       
   237 						send(st.reply(packet.data)
       
   238 							:tag("error", { type = "cancel" })
       
   239 								:tag("service-unavailable", { xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas" }));
       
   240 					end
       
   241 					print("   ...removing from send buffer");
       
   242 					sendbuffer.external[n] = nil;
       
   243 				end
       
   244 			end
       
   245 		end
       
   246 		
       
   247 		if #sendbuffer > 0 then 
       
   248 			for n, data in ipairs(sendbuffer) do
       
   249 				print "Sending..."
       
   250 				copas_send(conn, data);
       
   251 				print "Sent"
       
   252 				sendbuffer[n] = nil;
       
   253 			end
       
   254 		end
       
   255 		print "Receiving..."
       
   256 		repeat
       
   257 			reqdata, sktmsg = copas_receive(conn, 1);
       
   258 			if sktmsg == 'wantread' then
       
   259 				print("Received... wantread");
       
   260 				--socket.select({conn}, nil)
       
   261 				--print("Socket ready now...");
       
   262 			elseif sktmsg then
       
   263 				print("Received socket message:", sktmsg);
       
   264 			end
       
   265 		until reqdata or sktmsg == "closed";
       
   266 		print("Received", tostring(reqdata));
       
   267 	end
       
   268 	log("info", "core", "Client disconnected, connection closed");
       
   269 end
       
   270 
       
   271 server = socket.bind("*", 5223)
       
   272 assert(server, "Failed to bind to socket")
       
   273 copas.addserver(server, handler)
       
   274 
       
   275 copas.loop();