net.xmppcomponent_listener: Removed unnecessary and problematic cleanup code.
[prosody.git] / net / xmppcomponent_listener.lua
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
10 local hosts = _G.hosts;
11
12 local t_concat = table.concat;
13
14 local lxp = require "lxp";
15 local logger = require "util.logger";
16 local config = require "core.configmanager";
17 local connlisteners = require "net.connlisteners";
18 local cm_register_component = require "core.componentmanager".register_component;
19 local cm_deregister_component = require "core.componentmanager".deregister_component;
20 local uuid_gen = require "util.uuid".generate;
21 local jid_split = require "util.jid".split;
22 local sha1 = require "util.hashes".sha1;
23 local st = require "util.stanza";
24 local init_xmlhandlers = require "core.xmlhandlers";
25
26 local sessions = {};
27
28 local log = logger.init("componentlistener");
29
30 local component_listener = { default_port = 5347; default_mode = "*a"; default_interface = config.get("*", "core", "component_interface") or "127.0.0.1" };
31
32 local xmlns_component = 'jabber:component:accept';
33
34 --- Callbacks/data for xmlhandlers to handle streams for us ---
35
36 local stream_callbacks = { default_ns = xmlns_component };
37
38 local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
39
40 function stream_callbacks.error(session, error, data, data2)
41         if session.destroyed then return; end
42         log("warn", "Error processing component stream: "..tostring(error));
43         if error == "no-stream" then
44                 session:close("invalid-namespace");
45         elseif error == "parse-error" then
46                 session.log("warn", "External component %s XML parse error: %s", tostring(session.host), tostring(data));
47                 session:close("xml-not-well-formed");
48         elseif error == "stream-error" then
49                 local condition, text = "undefined-condition";
50                 for child in data:children() do
51                         if child.attr.xmlns == xmlns_xmpp_streams then
52                                 if child.name ~= "text" then
53                                         condition = child.name;
54                                 else
55                                         text = child:get_text();
56                                 end
57                                 if condition ~= "undefined-condition" and text then
58                                         break;
59                                 end
60                         end
61                 end
62                 text = condition .. (text and (" ("..text..")") or "");
63                 session.log("info", "Session closed by remote with error: %s", text);
64                 session:close(nil, text);
65         end
66 end
67
68 function stream_callbacks.streamopened(session, attr)
69         if config.get(attr.to, "core", "component_module") ~= "component" then
70                 -- Trying to act as a component domain which
71                 -- hasn't been configured
72                 session:close{ condition = "host-unknown", text = tostring(attr.to).." does not match any configured external components" };
73                 return;
74         end
75         
76         -- Note that we don't create the internal component
77         -- until after the external component auths successfully
78
79         session.host = attr.to;
80         session.streamid = uuid_gen();
81         session.notopen = nil;
82         
83         session.send(st.stanza("stream:stream", { xmlns=xmlns_component,
84                         ["xmlns:stream"]='http://etherx.jabber.org/streams', id=session.streamid, from=session.host }):top_tag());
85
86 end
87
88 function stream_callbacks.streamclosed(session)
89         session.log("debug", "Received </stream:stream>");
90         session:close();
91 end
92
93 local core_process_stanza = core_process_stanza;
94
95 function stream_callbacks.handlestanza(session, stanza)
96         -- Namespaces are icky.
97         if not stanza.attr.xmlns and stanza.name == "handshake" then
98                 stanza.attr.xmlns = xmlns_component;
99         end
100         if not stanza.attr.xmlns or stanza.attr.xmlns == "jabber:client" then
101                 local from = stanza.attr.from;
102                 if from then
103                         if session.component_validate_from then
104                                 local _, domain = jid_split(stanza.attr.from);
105                                 if domain ~= session.host then
106                                         -- Return error
107                                         session.log("warn", "Component sent stanza with missing or invalid 'from' address");
108                                         session:close{
109                                                 condition = "invalid-from";
110                                                 text = "Component tried to send from address <"..tostring(from)
111                                                            .."> which is not in domain <"..tostring(session.host)..">";
112                                         };
113                                         return;
114                                 end
115                         end
116                 else
117                         stanza.attr.from = session.host;
118                 end
119                 if not stanza.attr.to then
120                         session.log("warn", "Rejecting stanza with no 'to' address");
121                         session.send(st.error_reply(stanza, "modify", "bad-request", "Components MUST specify a 'to' address on stanzas"));
122                         return;
123                 end
124         end
125         return core_process_stanza(session, stanza);
126 end
127
128 --- Closing a component connection
129 local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
130 local default_stream_attr = { ["xmlns:stream"] = "http://etherx.jabber.org/streams", xmlns = stream_callbacks.default_ns, version = "1.0", id = "" };
131 local function session_close(session, reason)
132         if session.destroyed then return; end
133         local log = session.log or log;
134         if session.conn then
135                 if session.notopen then
136                         session.send("<?xml version='1.0'?>");
137                         session.send(st.stanza("stream:stream", default_stream_attr):top_tag());
138                 end
139                 if reason then
140                         if type(reason) == "string" then -- assume stream error
141                                 log("info", "Disconnecting component, <stream:error> is: %s", reason);
142                                 session.send(st.stanza("stream:error"):tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' }));
143                         elseif type(reason) == "table" then
144                                 if reason.condition then
145                                         local stanza = st.stanza("stream:error"):tag(reason.condition, stream_xmlns_attr):up();
146                                         if reason.text then
147                                                 stanza:tag("text", stream_xmlns_attr):text(reason.text):up();
148                                         end
149                                         if reason.extra then
150                                                 stanza:add_child(reason.extra);
151                                         end
152                                         log("info", "Disconnecting component, <stream:error> is: %s", tostring(stanza));
153                                         session.send(stanza);
154                                 elseif reason.name then -- a stanza
155                                         log("info", "Disconnecting component, <stream:error> is: %s", tostring(reason));
156                                         session.send(reason);
157                                 end
158                         end
159                 end
160                 session.send("</stream:stream>");
161                 session.conn:close();
162                 component_listener.ondisconnect(session.conn, "stream error");
163         end
164 end
165
166 --- Component connlistener
167 function component_listener.onincoming(conn, data)
168         local session = sessions[conn];
169         if not session then
170                 local _send = conn.write;
171                 session = { type = "component", conn = conn, send = function (data) return _send(conn, tostring(data)); end };
172                 sessions[conn] = session;
173
174                 -- Logging functions --
175                 
176                 local conn_name = "jcp"..tostring(conn):match("[a-f0-9]+$");
177                 session.log = logger.init(conn_name);
178                 session.close = session_close;
179                 
180                 session.log("info", "Incoming Jabber component connection");
181                 
182                 local parser = lxp.new(init_xmlhandlers(session, stream_callbacks), "\1");
183                 session.parser = parser;
184                 
185                 session.notopen = true;
186                 
187                 function session.data(conn, data)
188                         local ok, err = parser:parse(data);
189                         if ok then return; end
190                         log("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "):gsub("[%z\1-\31]", "_"));
191                         session:close("xml-not-well-formed");
192                 end
193                 
194                 session.dispatch_stanza = stream_callbacks.handlestanza;
195                 
196         end
197         if data then
198                 session.data(conn, data);
199         end
200 end
201         
202 function component_listener.ondisconnect(conn, err)
203         local session = sessions[conn];
204         if session then
205                 (session.log or log)("info", "component disconnected: %s (%s)", tostring(session.host), tostring(err));
206                 if session.on_destroy then session:on_destroy(err); end
207                 sessions[conn] = nil;
208                 for k in pairs(session) do
209                         if k ~= "log" and k ~= "close" then
210                                 session[k] = nil;
211                         end
212                 end
213                 session.destroyed = true;
214                 session = nil;
215         end
216 end
217
218 connlisteners.register('xmppcomponent', component_listener);