Merge from waqas
[prosody.git] / net / xmppserver_listener.lua
1 -- Prosody IM v0.1
2 -- Copyright (C) 2008 Matthew Wild
3 -- Copyright (C) 2008 Waqas Hussain
4 -- 
5 -- This program is free software; you can redistribute it and/or
6 -- modify it under the terms of the GNU General Public License
7 -- as published by the Free Software Foundation; either version 2
8 -- of the License, or (at your option) any later version.
9 -- 
10 -- This program is distributed in the hope that it will be useful,
11 -- but WITHOUT ANY WARRANTY; without even the implied warranty of
12 -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 -- GNU General Public License for more details.
14 -- 
15 -- You should have received a copy of the GNU General Public License
16 -- along with this program; if not, write to the Free Software
17 -- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18 --
19
20
21
22 local logger = require "logger";
23 local lxp = require "lxp"
24 local init_xmlhandlers = require "core.xmlhandlers"
25 local sm_new_session = require "core.sessionmanager".new_session;
26 local s2s_new_incoming = require "core.s2smanager".new_incoming;
27 local s2s_streamopened = require "core.s2smanager".streamopened;
28 local s2s_streamclosed = require "core.s2smanager".streamclosed;
29 local s2s_destroy_session = require "core.s2smanager".destroy_session;
30 local s2s_attempt_connect = require "core.s2smanager".attempt_connection;
31 local stream_callbacks = { streamopened = s2s_streamopened, streamclosed = s2s_streamclosed, handlestanza =  core_process_stanza };
32
33 local connlisteners_register = require "net.connlisteners".register;
34
35 local t_insert = table.insert;
36 local t_concat = table.concat;
37 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
38 local m_random = math.random;
39 local format = string.format;
40 local sm_new_session, sm_destroy_session = sessionmanager.new_session, sessionmanager.destroy_session; --import("core.sessionmanager", "new_session", "destroy_session");
41 local st = stanza;
42
43 local sessions = {};
44 local xmppserver = { default_port = 5269, default_mode = "*a" };
45
46 -- These are session methods --
47
48 local function session_reset_stream(session)
49         -- Reset stream
50                 local parser = lxp.new(init_xmlhandlers(session, stream_callbacks), "|");
51                 session.parser = parser;
52                 
53                 session.notopen = true;
54                 
55                 function session.data(conn, data)
56                         parser:parse(data);
57                 end
58                 return true;
59 end
60
61
62 local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
63 local function session_close(session, reason)
64         local log = session.log or log;
65         if session.conn then
66                 if reason then
67                         if type(reason) == "string" then -- assume stream error
68                                 log("info", "Disconnecting %s[%s], <stream:error> is: %s", session.host or "(unknown host)", session.type, reason);
69                                 session.sends2s(st.stanza("stream:error"):tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' }));
70                         elseif type(reason) == "table" then
71                                 if reason.condition then
72                                         local stanza = st.stanza("stream:error"):tag(reason.condition, stream_xmlns_attr):up();
73                                         if reason.text then
74                                                 stanza:tag("text", stream_xmlns_attr):text(reason.text):up();
75                                         end
76                                         if reason.extra then
77                                                 stanza:add_child(reason.extra);
78                                         end
79                                         log("info", "Disconnecting %s[%s], <stream:error> is: %s", session.host or "(unknown host)", session.type, tostring(stanza));
80                                         session.sends2s(stanza);
81                                 elseif reason.name then -- a stanza
82                                         log("info", "Disconnecting %s->%s[%s], <stream:error> is: %s", session.from_host or "(unknown host)", session.to_host or "(unknown host)", session.type, tostring(reason));
83                                         session.sends2s(reason);
84                                 end
85                         end
86                 end
87                 session.sends2s("</stream:stream>");
88                 session.conn.close();
89                 xmppserver.disconnect(session.conn, "stream error");
90         end
91 end
92
93
94 -- End of session methods --
95
96 function xmppserver.listener(conn, data)
97         local session = sessions[conn];
98         if not session then
99                 session = s2s_new_incoming(conn);
100                 sessions[conn] = session;
101
102                 -- Logging functions --
103
104                 local mainlog, log = log;
105                 do
106                         local conn_name = "s2sin"..tostring(conn):match("[a-f0-9]+$");
107                         log = logger.init(conn_name);
108                 end
109                 local print = function (...) log("info", t_concatall({...}, "\t")); end
110                 session.log = log;
111
112                 print("Incoming s2s connection");
113                 
114                 session.reset_stream = session_reset_stream;
115                 session.close = session_close;
116                 
117                 session_reset_stream(session); -- Initialise, ready for use
118                 
119                 -- FIXME: Below function should be session,stanza - and xmlhandlers should use :method() notation to call,
120                 -- this will avoid the useless indirection we have atm
121                 -- (I'm on a mission, no time to fix now)
122
123                 -- Debug version --
124 --              local function handleerr(err) print("Traceback:", err, debug.traceback()); end
125 --              session.stanza_dispatch = function (stanza) return select(2, xpcall(function () return core_process_stanza(session, stanza); end, handleerr));  end
126         end
127         if data then
128                 session.data(conn, data);
129         end
130 end
131         
132 function xmppserver.disconnect(conn, err)
133         local session = sessions[conn];
134         if session then
135                 if err and err ~= "closed" and session.srv_hosts then
136                         if s2s_attempt_connect(session, err) then
137                                 return; -- Session lives for now
138                         end
139                 end
140                 (session.log or log)("info", "s2s disconnected: %s->%s (%s)", tostring(session.from_host), tostring(session.to_host), tostring(err));
141                 s2s_destroy_session(session);
142                 sessions[conn]  = nil;
143                 session = nil;
144                 collectgarbage("collect");
145         end
146 end
147
148 function xmppserver.register_outgoing(conn, session)
149         session.direction = "outgoing";
150         sessions[conn] = session;
151         
152         session.reset_stream = session_reset_stream;    
153         session_reset_stream(session); -- Initialise, ready for use
154         
155         -- FIXME: Below function should be session,stanza - and xmlhandlers should use :method() notation to call,
156         -- this will avoid the useless indirection we have atm
157         -- (I'm on a mission, no time to fix now)
158         local function handleerr(err) print("Traceback:", err, debug.traceback()); end
159         session.stanza_dispatch = function (stanza) return select(2, xpcall(function () return core_process_stanza(session, stanza); end, handleerr));  end
160 end
161
162 connlisteners_register("xmppserver", xmppserver);
163
164
165 -- We need to perform some initialisation when a connection is created
166 -- We also need to perform that same initialisation at other points (SASL, TLS, ...)
167
168 -- ...and we need to handle data
169 -- ...and record all sessions associated with connections