31 |
31 |
32 local component_listener = { default_port = 5347; default_mode = "*a"; default_interface = config.get("*", "core", "component_interface") or "127.0.0.1" }; |
32 local component_listener = { default_port = 5347; default_mode = "*a"; default_interface = config.get("*", "core", "component_interface") or "127.0.0.1" }; |
33 |
33 |
34 local xmlns_component = 'jabber:component:accept'; |
34 local xmlns_component = 'jabber:component:accept'; |
35 |
35 |
36 --- Callbacks/data for xmlhandlers to handle streams for us --- |
|
37 |
|
38 local stream_callbacks = { stream_tag = "http://etherx.jabber.org/streams|stream", default_ns = xmlns_component }; |
|
39 |
|
40 function stream_callbacks.error(session, error, data, data2) |
|
41 log("warn", "Error processing component stream: "..tostring(error)); |
|
42 if error == "no-stream" then |
|
43 session:close("invalid-namespace"); |
|
44 elseif error == "xml-parse-error" and data == "unexpected-element-close" then |
|
45 session.log("debug", "Unexpected close of '%s' tag", data2); |
|
46 session:close("xml-not-well-formed"); |
|
47 else |
|
48 session.log("debug", "External component %s XML parse error: %s", tostring(session.host), tostring(error)); |
|
49 print(data, debug.traceback()) |
|
50 session:close("xml-not-well-formed"); |
|
51 end |
|
52 end |
|
53 |
|
54 function stream_callbacks.streamopened(session, attr) |
|
55 if config.get(attr.to, "core", "component_module") ~= "component" then |
|
56 -- Trying to act as a component domain which |
|
57 -- hasn't been configured |
|
58 session:close{ condition = "host-unknown", text = tostring(attr.to).." does not match any configured external components" }; |
|
59 return; |
|
60 end |
|
61 |
|
62 -- Store the original host (this is used for config, etc.) |
|
63 session.user = attr.to; |
|
64 -- Set the host for future reference |
|
65 session.host = config.get(attr.to, "core", "component_address") or attr.to; |
|
66 -- Note that we don't create the internal component |
|
67 -- until after the external component auths successfully |
|
68 |
|
69 session.streamid = uuid_gen(); |
|
70 session.notopen = nil; |
|
71 |
|
72 session.send(st.stanza("stream:stream", { xmlns=xmlns_component, |
|
73 ["xmlns:stream"]='http://etherx.jabber.org/streams', id=session.streamid, from=session.host }):top_tag()); |
|
74 |
|
75 end |
|
76 |
|
77 function stream_callbacks.streamclosed(session) |
|
78 session.send("</stream:stream>"); |
|
79 session.notopen = true; |
|
80 end |
|
81 |
|
82 local core_process_stanza = core_process_stanza; |
|
83 |
|
84 function stream_callbacks.handlestanza(session, stanza) |
|
85 -- Namespaces are icky. |
|
86 log("warn", "Handing stanza with name %s", stanza.name); |
|
87 if stanza.name ~= "handshake" then |
|
88 return core_process_stanza(session, stanza); |
|
89 else |
|
90 handle_component_auth(session, stanza); |
|
91 end |
|
92 end |
|
93 |
|
94 --- Handle authentication attempts by components |
36 --- Handle authentication attempts by components |
95 function handle_component_auth(session, stanza) |
37 function handle_component_auth(session, stanza) |
|
38 log("info", "Handling component auth"); |
96 if (not session.host) or #stanza.tags > 0 then |
39 if (not session.host) or #stanza.tags > 0 then |
|
40 (session.log or log)("warn", "Component handshake invalid"); |
97 session:close("not-authorized"); |
41 session:close("not-authorized"); |
98 return; |
42 return; |
99 end |
43 end |
100 |
44 |
101 local secret = config.get(session.user, "core", "component_secret"); |
45 local secret = config.get(session.user, "core", "component_secret"); |
102 if not secret then |
46 if not secret then |
103 log("warn", "Component attempted to identify as %s, but component_password is not set", session.user); |
47 (session.log or log)("warn", "Component attempted to identify as %s, but component_password is not set", session.user); |
104 session:close("not-authorized"); |
48 session:close("not-authorized"); |
105 return; |
49 return; |
106 end |
50 end |
107 |
51 |
108 local supplied_token = t_concat(stanza); |
52 local supplied_token = t_concat(stanza); |
109 local calculated_token = sha1(session.streamid..secret, true); |
53 local calculated_token = sha1(session.streamid..secret, true); |
110 if supplied_token:lower() ~= calculated_token:lower() then |
54 if supplied_token:lower() ~= calculated_token:lower() then |
|
55 log("info", "Component for %s authentication failed", session.host); |
111 session:close{ condition = "not-authorized", text = "Given token does not match calculated token" }; |
56 session:close{ condition = "not-authorized", text = "Given token does not match calculated token" }; |
112 return; |
57 return; |
113 end |
58 end |
114 |
59 |
115 |
60 |
116 -- Authenticated now |
61 -- Authenticated now |
|
62 log("info", "Component authenticated: %s", session.host); |
117 |
63 |
118 -- If component not already created for this host, create one now |
64 -- If component not already created for this host, create one now |
119 if not hosts[session.host].connected then |
65 if not hosts[session.host].connected then |
120 local send = session.send; |
66 local send = session.send; |
121 session.component_session = cm_register_component(session.host, function (_, data) return send(data); end); |
67 session.component_session = cm_register_component(session.host, function (_, data) return send(data); end); |
122 hosts[session.host].connected = true; |
68 hosts[session.host].connected = true; |
|
69 log("info", "Component successfully registered"); |
123 else |
70 else |
124 log("error", "Multiple components bound to the same address, first one wins (TODO: Implement stanza distribution)"); |
71 log("error", "Multiple components bound to the same address, first one wins (TODO: Implement stanza distribution)"); |
125 end |
72 end |
126 |
73 |
127 -- Signal successful authentication |
74 -- Signal successful authentication |
128 session.send(st.stanza("handshake")); |
75 session.send(st.stanza("handshake")); |
129 end |
76 end |
130 |
77 |
131 module:add_handler("component", "handshake", xmlns_component, handle_component_auth); |
78 module:add_handler("component", "handshake", xmlns_component, handle_component_auth); |
132 |
|
133 --- Closing a component connection |
|
134 local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'}; |
|
135 local function session_close(session, reason) |
|
136 local log = session.log or log; |
|
137 if session.conn then |
|
138 if reason then |
|
139 if type(reason) == "string" then -- assume stream error |
|
140 log("info", "Disconnecting component, <stream:error> is: %s", reason); |
|
141 session.send(st.stanza("stream:error"):tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' })); |
|
142 elseif type(reason) == "table" then |
|
143 if reason.condition then |
|
144 local stanza = st.stanza("stream:error"):tag(reason.condition, stream_xmlns_attr):up(); |
|
145 if reason.text then |
|
146 stanza:tag("text", stream_xmlns_attr):text(reason.text):up(); |
|
147 end |
|
148 if reason.extra then |
|
149 stanza:add_child(reason.extra); |
|
150 end |
|
151 log("info", "Disconnecting component, <stream:error> is: %s", tostring(stanza)); |
|
152 session.send(stanza); |
|
153 elseif reason.name then -- a stanza |
|
154 log("info", "Disconnecting component, <stream:error> is: %s", tostring(reason)); |
|
155 session.send(reason); |
|
156 end |
|
157 end |
|
158 end |
|
159 session.send("</stream:stream>"); |
|
160 session.conn.close(); |
|
161 component_listener.disconnect(session.conn, "stream error"); |
|
162 end |
|
163 end |
|
164 |
|
165 --- Component connlistener |
|
166 function component_listener.listener(conn, data) |
|
167 local session = sessions[conn]; |
|
168 if not session then |
|
169 local _send = conn.write; |
|
170 session = { type = "component", conn = conn, send = function (data) return _send(tostring(data)); end }; |
|
171 sessions[conn] = session; |
|
172 |
|
173 -- Logging functions -- |
|
174 |
|
175 local conn_name = "xep114-"..tostring(conn):match("[a-f0-9]+$"); |
|
176 session.log = logger.init(conn_name); |
|
177 session.close = session_close; |
|
178 |
|
179 session.log("info", "Incoming XEP-0114 connection"); |
|
180 |
|
181 local parser = lxp.new(init_xmlhandlers(session, stream_callbacks), "|"); |
|
182 session.parser = parser; |
|
183 |
|
184 session.notopen = true; |
|
185 |
|
186 function session.data(conn, data) |
|
187 local ok, err = parser:parse(data); |
|
188 if ok then return; end |
|
189 session:close("xml-not-well-formed"); |
|
190 end |
|
191 |
|
192 session.dispatch_stanza = stream_callbacks.handlestanza; |
|
193 |
|
194 end |
|
195 if data then |
|
196 session.data(conn, data); |
|
197 end |
|
198 end |
|
199 |
|
200 function component_listener.disconnect(conn, err) |
|
201 local session = sessions[conn]; |
|
202 if session then |
|
203 (session.log or log)("info", "component disconnected: %s (%s)", tostring(session.host), tostring(err)); |
|
204 if session.host then |
|
205 log("debug", "deregistering component"); |
|
206 cm_deregister_component(session.host); |
|
207 hosts[session.host].connected = nil; |
|
208 end |
|
209 sessions[conn] = nil; |
|
210 session = nil; |
|
211 collectgarbage("collect"); |
|
212 end |
|
213 end |
|
214 |
|
215 connlisteners.register('component', component_listener); |
|
216 |
|
217 module:add_event_hook("server-started", |
|
218 function () |
|
219 if _G.net_activate_ports then |
|
220 _G.net_activate_ports("component", "component", {5437}, "tcp"); |
|
221 else |
|
222 error("No net_activate_ports: Using an incompatible version of Prosody?"); |
|
223 end |
|
224 end); |
|