plugins/mod_c2s.lua
changeset 4543 db27a4c18b6a
child 4548 e6e5c76ff009
equal deleted inserted replaced
4542:50aca1e0bfbd 4543:db27a4c18b6a
       
     1 -- Prosody IM
       
     2 -- Copyright (C) 2008-2010 Matthew Wild
       
     3 -- Copyright (C) 2008-2010 Waqas Hussain
       
     4 -- 
       
     5 -- This project is MIT/X11 licensed. Please see the
       
     6 -- COPYING file in the source package for more information.
       
     7 --
       
     8 
       
     9 module:set_global();
       
    10 
       
    11 local new_xmpp_stream = require "util.xmppstream".new;
       
    12 local nameprep = require "util.encodings".stringprep.nameprep;
       
    13 local portmanager = require "core.portmanager";
       
    14 local sessionmanager = require "core.sessionmanager";
       
    15 local st = require "util.stanza";
       
    16 local sm_new_session, sm_destroy_session = sessionmanager.new_session, sessionmanager.destroy_session;
       
    17 local uuid_generate = require "util.uuid".generate;
       
    18 
       
    19 local xpcall, tostring, type = xpcall, tostring, type;
       
    20 local format = string.format;
       
    21 local traceback = debug.traceback;
       
    22 
       
    23 local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
       
    24 
       
    25 local log = module._log;
       
    26 
       
    27 local opt_keepalives = module:get_option_boolean("tcp_keepalives", false);
       
    28 
       
    29 local sessions = module:shared("sessions");
       
    30 
       
    31 local stream_callbacks = { default_ns = "jabber:client", handlestanza = core_process_stanza };
       
    32 local listener = { default_port = 5222, default_mode = "*a" };
       
    33 
       
    34 --- Stream events handlers
       
    35 local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
       
    36 local default_stream_attr = { ["xmlns:stream"] = "http://etherx.jabber.org/streams", xmlns = stream_callbacks.default_ns, version = "1.0", id = "" };
       
    37 
       
    38 function stream_callbacks.streamopened(session, attr)
       
    39 	local send = session.send;
       
    40 	session.host = attr.to;
       
    41 	if not session.host then
       
    42 		session:close{ condition = "improper-addressing",
       
    43 			text = "A 'to' attribute is required on stream headers" };
       
    44 		return;
       
    45 	end
       
    46 	session.host = nameprep(session.host);
       
    47 	session.version = tonumber(attr.version) or 0;
       
    48 	session.streamid = uuid_generate();
       
    49 	(session.log or session)("debug", "Client sent opening <stream:stream> to %s", session.host);
       
    50 
       
    51 	if not hosts[session.host] then
       
    52 		-- We don't serve this host...
       
    53 		session:close{ condition = "host-unknown", text = "This server does not serve "..tostring(session.host)};
       
    54 		return;
       
    55 	end
       
    56 
       
    57 	send("<?xml version='1.0'?>");
       
    58 	send(format("<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='%s' from='%s' version='1.0' xml:lang='en'>", session.streamid, session.host));
       
    59 
       
    60 	(session.log or log)("debug", "Sent reply <stream:stream> to client");
       
    61 	session.notopen = nil;
       
    62 
       
    63 	-- If session.secure is *false* (not nil) then it means we /were/ encrypting
       
    64 	-- since we now have a new stream header, session is secured
       
    65 	if session.secure == false then
       
    66 		session.secure = true;
       
    67 	end
       
    68 
       
    69 	local features = st.stanza("stream:features");
       
    70 	hosts[session.host].events.fire_event("stream-features", { origin = session, features = features });
       
    71 	module:fire_event("stream-features", session, features);
       
    72 
       
    73 	send(features);
       
    74 end
       
    75 
       
    76 function stream_callbacks.streamclosed(session)
       
    77 	session.log("debug", "Received </stream:stream>");
       
    78 	session:close();
       
    79 end
       
    80 
       
    81 function stream_callbacks.error(session, error, data)
       
    82 	if error == "no-stream" then
       
    83 		session.log("debug", "Invalid opening stream header");
       
    84 		session:close("invalid-namespace");
       
    85 	elseif error == "parse-error" then
       
    86 		(session.log or log)("debug", "Client XML parse error: %s", tostring(data));
       
    87 		session:close("not-well-formed");
       
    88 	elseif error == "stream-error" then
       
    89 		local condition, text = "undefined-condition";
       
    90 		for child in data:children() do
       
    91 			if child.attr.xmlns == xmlns_xmpp_streams then
       
    92 				if child.name ~= "text" then
       
    93 					condition = child.name;
       
    94 				else
       
    95 					text = child:get_text();
       
    96 				end
       
    97 				if condition ~= "undefined-condition" and text then
       
    98 					break;
       
    99 				end
       
   100 			end
       
   101 		end
       
   102 		text = condition .. (text and (" ("..text..")") or "");
       
   103 		session.log("info", "Session closed by remote with error: %s", text);
       
   104 		session:close(nil, text);
       
   105 	end
       
   106 end
       
   107 
       
   108 local function handleerr(err) log("error", "Traceback[c2s]: %s: %s", tostring(err), traceback()); end
       
   109 function stream_callbacks.handlestanza(session, stanza)
       
   110 	stanza = session.filter("stanzas/in", stanza);
       
   111 	if stanza then
       
   112 		return xpcall(function () return core_process_stanza(session, stanza) end, handleerr);
       
   113 	end
       
   114 end
       
   115 
       
   116 --- Session methods
       
   117 local function session_close(session, reason)
       
   118 	local log = session.log or log;
       
   119 	if session.conn then
       
   120 		if session.notopen then
       
   121 			session.send("<?xml version='1.0'?>");
       
   122 			session.send(st.stanza("stream:stream", default_stream_attr):top_tag());
       
   123 		end
       
   124 		if reason then
       
   125 			if type(reason) == "string" then -- assume stream error
       
   126 				log("info", "Disconnecting client, <stream:error> is: %s", reason);
       
   127 				session.send(st.stanza("stream:error"):tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' }));
       
   128 			elseif type(reason) == "table" then
       
   129 				if reason.condition then
       
   130 					local stanza = st.stanza("stream:error"):tag(reason.condition, stream_xmlns_attr):up();
       
   131 					if reason.text then
       
   132 						stanza:tag("text", stream_xmlns_attr):text(reason.text):up();
       
   133 					end
       
   134 					if reason.extra then
       
   135 						stanza:add_child(reason.extra);
       
   136 					end
       
   137 					log("info", "Disconnecting client, <stream:error> is: %s", tostring(stanza));
       
   138 					session.send(stanza);
       
   139 				elseif reason.name then -- a stanza
       
   140 					log("info", "Disconnecting client, <stream:error> is: %s", tostring(reason));
       
   141 					session.send(reason);
       
   142 				end
       
   143 			end
       
   144 		end
       
   145 		session.send("</stream:stream>");
       
   146 		session.conn:close();
       
   147 		listener.ondisconnect(session.conn, (reason and (reason.text or reason.condition)) or reason or "session closed");
       
   148 	end
       
   149 end
       
   150 
       
   151 --- Port listener
       
   152 function listener.onconnect(conn)
       
   153 	local session = sm_new_session(conn);
       
   154 	sessions[conn] = session;
       
   155 	
       
   156 	session.log("info", "Client connected");
       
   157 	
       
   158 	-- Client is using legacy SSL (otherwise mod_tls sets this flag)
       
   159 	if conn:ssl() then
       
   160 		session.secure = true;
       
   161 	end
       
   162 	
       
   163 	if opt_keepalives then
       
   164 		conn:setoption("keepalive", opt_keepalives);
       
   165 	end
       
   166 	
       
   167 	session.close = session_close;
       
   168 	
       
   169 	local stream = new_xmpp_stream(session, stream_callbacks);
       
   170 	session.stream = stream;
       
   171 	session.notopen = true;
       
   172 	
       
   173 	function session.reset_stream()
       
   174 		session.notopen = true;
       
   175 		session.stream:reset();
       
   176 	end
       
   177 	
       
   178 	local filter = session.filter;
       
   179 	function session.data(data)
       
   180 		data = filter("bytes/in", data);
       
   181 		if data then
       
   182 			local ok, err = stream:feed(data);
       
   183 			if ok then return; end
       
   184 			log("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "):gsub("[%z\1-\31]", "_"));
       
   185 			session:close("not-well-formed");
       
   186 		end
       
   187 	end
       
   188 
       
   189 	session.dispatch_stanza = stream_callbacks.handlestanza;
       
   190 end
       
   191 
       
   192 function listener.onincoming(conn, data)
       
   193 	local session = sessions[conn];
       
   194 	if session then
       
   195 		session.data(data);
       
   196 	end
       
   197 end
       
   198 
       
   199 function listener.ondisconnect(conn, err)
       
   200 	local session = sessions[conn];
       
   201 	if session then
       
   202 		(session.log or log)("info", "Client disconnected: %s", err);
       
   203 		sm_destroy_session(session, err);
       
   204 		sessions[conn]  = nil;
       
   205 		session = nil;
       
   206 	end
       
   207 end
       
   208 
       
   209 function listener.associate_session(conn, session)
       
   210 	sessions[conn] = session;
       
   211 end
       
   212 
       
   213 portmanager.register_service("c2s", {
       
   214 	listener = listener;
       
   215 	default_port = 5222;
       
   216 	encryption = "starttls";
       
   217 });
       
   218 
       
   219 portmanager.register_service("legacy_ssl", {
       
   220 	listener = listener;
       
   221 	encryption = "ssl";
       
   222 });
       
   223 
       
   224