2 -- Copyright (C) 2008-2010 Matthew Wild
3 -- Copyright (C) 2008-2010 Waqas Hussain
5 -- This project is MIT/X11 licensed. Please see the
6 -- COPYING file in the source package for more information.
9 local tostring, setmetatable = tostring, setmetatable;
10 local pairs, next= pairs, next;
13 local full_sessions = full_sessions;
14 local bare_sessions = bare_sessions;
16 local logger = require "util.logger";
17 local log = logger.init("sessionmanager");
18 local rm_load_roster = require "core.rostermanager".load_roster;
19 local config_get = require "core.configmanager".get;
20 local resourceprep = require "util.encodings".stringprep.resourceprep;
21 local nodeprep = require "util.encodings".stringprep.nodeprep;
22 local uuid_generate = require "util.uuid".generate;
24 local initialize_filters = require "util.filters".initialize;
25 local gettime = require "socket".gettime;
27 local newproxy = newproxy;
28 local getmetatable = getmetatable;
30 module "sessionmanager"
32 local open_sessions = 0;
34 function new_session(conn)
35 local session = { conn = conn, type = "c2s_unauthed", conntime = gettime() };
37 session.trace = newproxy(true);
38 getmetatable(session.trace).__gc = function () open_sessions = open_sessions - 1; end;
40 open_sessions = open_sessions + 1;
41 log("debug", "open sessions now: %d", open_sessions);
43 local filter = initialize_filters(session);
45 session.send = function (t)
47 t = filter("stanzas/out", t);
50 t = filter("bytes/out", tostring(t));
56 session.ip = conn:ip();
57 local conn_name = "c2s"..tostring(conn):match("[a-f0-9]+$");
58 session.log = logger.init(conn_name);
63 local resting_session = { -- Resting, not dead
65 type = "c2s_destroyed";
66 close = function (session)
67 session.log("debug", "Attempt to close already-closed session");
69 filter = function (type, data) return data; end;
70 }; resting_session.__index = resting_session;
72 function retire_session(session)
73 local log = session.log or log;
74 for k in pairs(session) do
75 if k ~= "trace" and k ~= "log" and k ~= "id" then
80 function session.send(data) log("debug", "Discarding data sent to resting session: %s", tostring(data)); return false; end
81 function session.data(data) log("debug", "Discarding data received from resting session: %s", tostring(data)); end
82 return setmetatable(session, resting_session);
85 function destroy_session(session, err)
86 (session.log or log)("info", "Destroying session for %s (%s@%s)%s", session.full_jid or "(unknown)", session.username or "(unknown)", session.host or "(unknown)", err and (": "..err) or "");
87 if session.destroyed then return; end
89 -- Remove session/resource from user's session list
90 if session.full_jid then
91 local host_session = hosts[session.host];
93 -- Allow plugins to prevent session destruction
94 if host_session.events.fire_event("pre-resource-unbind", {session=session, error=err}) then
98 host_session.sessions[session.username].sessions[session.resource] = nil;
99 full_sessions[session.full_jid] = nil;
101 if not next(host_session.sessions[session.username].sessions) then
102 log("debug", "All resources of %s are now offline", session.username);
103 host_session.sessions[session.username] = nil;
104 bare_sessions[session.username..'@'..session.host] = nil;
107 host_session.events.fire_event("resource-unbind", {session=session, error=err});
110 retire_session(session);
113 function make_authenticated(session, username)
114 username = nodeprep(username);
115 if not username or #username == 0 then return nil, "Invalid username"; end
116 session.username = username;
117 if session.type == "c2s_unauthed" then
118 session.type = "c2s";
120 session.log("info", "Authenticated as %s@%s", username or "(unknown)", session.host or "(unknown)");
124 -- returns true, nil on success
125 -- returns nil, err_type, err, err_message on failure
126 function bind_resource(session, resource)
127 if not session.username then return nil, "auth", "not-authorized", "Cannot bind resource before authentication"; end
128 if session.resource then return nil, "cancel", "already-bound", "Cannot bind multiple resources on a single connection"; end
129 -- We don't support binding multiple resources
131 resource = resourceprep(resource);
132 resource = resource ~= "" and resource or uuid_generate();
133 --FIXME: Randomly-generated resources must be unique per-user, and never conflict with existing
135 if not hosts[session.host].sessions[session.username] then
136 local sessions = { sessions = {} };
137 hosts[session.host].sessions[session.username] = sessions;
138 bare_sessions[session.username..'@'..session.host] = sessions;
140 local sessions = hosts[session.host].sessions[session.username].sessions;
141 if sessions[resource] then
143 local policy = config_get(session.host, "core", "conflict_resolve");
145 if policy == "random" then
146 resource = uuid_generate();
148 elseif policy == "increment" then
149 increment = true; -- TODO ping old resource
150 elseif policy == "kick_new" then
151 return nil, "cancel", "conflict", "Resource already exists";
152 else -- if policy == "kick_old" then
153 sessions[resource]:close {
154 condition = "conflict";
155 text = "Replaced by new connection";
157 if not next(sessions) then
158 hosts[session.host].sessions[session.username] = { sessions = sessions };
159 bare_sessions[session.username.."@"..session.host] = hosts[session.host].sessions[session.username];
162 if increment and sessions[resource] then
164 while sessions[resource.."#"..count] do
167 resource = resource.."#"..count;
172 session.resource = resource;
173 session.full_jid = session.username .. '@' .. session.host .. '/' .. resource;
174 hosts[session.host].sessions[session.username].sessions[resource] = session;
175 full_sessions[session.full_jid] = session;
178 session.roster, err = rm_load_roster(session.username, session.host);
180 full_sessions[session.full_jid] = nil;
181 hosts[session.host].sessions[session.username].sessions[resource] = nil;
182 session.full_jid = nil;
183 session.resource = nil;
184 if next(bare_sessions[session.username..'@'..session.host].sessions) == nil then
185 bare_sessions[session.username..'@'..session.host] = nil;
186 hosts[session.host].sessions[session.username] = nil;
188 return nil, "cancel", "internal-server-error", "Error loading roster";
191 hosts[session.host].events.fire_event("resource-bind", {session=session});
196 function send_to_available_resources(user, host, stanza)
197 local jid = user.."@"..host;
199 local user = bare_sessions[jid];
201 for k, session in pairs(user.sessions) do
202 if session.presence then
203 session.send(stanza);
211 function send_to_interested_resources(user, host, stanza)
212 local jid = user.."@"..host;
214 local user = bare_sessions[jid];
216 for k, session in pairs(user.sessions) do
217 if session.interested then
218 session.send(stanza);