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.
8 -- luacheck: globals prosody.full_sessions prosody.bare_sessions
10 local tostring, setmetatable
= tostring, setmetatable
;
11 local pairs
, next= pairs
, next;
13 local hosts
= prosody
.hosts
;
14 local full_sessions
= prosody
.full_sessions
;
15 local bare_sessions
= prosody
.bare_sessions
;
17 local logger
= require
"util.logger";
18 local log = logger
.init("sessionmanager");
19 local rm_load_roster
= require
"core.rostermanager".load_roster
;
20 local config_get
= require
"core.configmanager".get
;
21 local resourceprep
= require
"util.encodings".stringprep
.resourceprep
;
22 local nodeprep
= require
"util.encodings".stringprep
.nodeprep
;
23 local generate_identifier
= require
"util.id".short
;
24 local sessionlib
= require
"util.session";
26 local initialize_filters
= require
"util.filters".initialize
;
27 local gettime
= require
"socket".gettime
;
32 local function new_session(conn
)
33 local session
= sessionlib
.new("c2s");
34 sessionlib
.set_id(session
);
35 sessionlib
.set_logger(session
);
36 sessionlib
.set_conn(session
, conn
);
38 session
.conntime
= gettime();
39 local filter
= initialize_filters(session
);
42 function session
.rawsend(t
)
43 t
= filter("bytes/out", tostring(t
));
45 local ret
, err
= w(conn
, t
);
47 session
.log("debug", "Error writing to connection: %s", err
);
54 session
.send
= function (t
)
55 session
.log("debug", "Sending[%s]: %s", session
.type, t
.top_tag
and t
:top_tag() or t
:match("^[^>]*>?"));
57 t
= filter("stanzas/out", t
);
60 return session
.rawsend(t
);
64 session
.ip
= conn
:ip();
65 local conn_name
= "c2s"..tostring(session
):match("[a-f0-9]+$");
66 session
.log = logger
.init(conn_name
);
71 local resting_session
= { -- Resting, not dead
73 type = "c2s_destroyed";
74 close
= function (session
)
75 session
.log("debug", "Attempt to close already-closed session");
77 filter
= function (type, data
) return data
; end; --luacheck: ignore 212/type
78 }; resting_session
.__index
= resting_session
;
80 local function retire_session(session
)
81 local log = session
.log or log; --luacheck: ignore 431/log
82 for k
in pairs(session
) do
83 if k
~= "log" and k
~= "id" then
88 function session
.send(data
) log("debug", "Discarding data sent to resting session: %s", data
); return false; end
89 function session
.data(data
) log("debug", "Discarding data received from resting session: %s", data
); end
90 session
.thread
= { run
= function (_
, data
) return session
.data(data
) end };
91 return setmetatable(session
, resting_session
);
94 local function destroy_session(session
, err
)
95 (session
.log or log)("debug", "Destroying session for %s (%s@%s)%s",
96 session
.full_jid
or "(unknown)", session
.username
or "(unknown)",
97 session
.host
or "(unknown)", err
and (": "..err
) or "");
99 if session
.destroyed
then return; end
101 -- Remove session/resource from user's session list
102 if session
.full_jid
then
103 local host_session
= hosts
[session
.host
];
105 -- Allow plugins to prevent session destruction
106 if host_session
.events
.fire_event("pre-resource-unbind", {session
=session
, error=err
}) then
110 host_session
.sessions
[session
.username
].sessions
[session
.resource
] = nil;
111 full_sessions
[session
.full_jid
] = nil;
113 if not next(host_session
.sessions
[session
.username
].sessions
) then
114 log("debug", "All resources of %s are now offline", session
.username
);
115 host_session
.sessions
[session
.username
] = nil;
116 bare_sessions
[session
.username
..'@'..session
.host
] = nil;
119 host_session
.events
.fire_event("resource-unbind", {session
=session
, error=err
});
122 retire_session(session
);
125 local function make_authenticated(session
, username
)
126 username
= nodeprep(username
);
127 if not username
or #username
== 0 then return nil, "Invalid username"; end
128 session
.username
= username
;
129 if session
.type == "c2s_unauthed" then
130 session
.type = "c2s_unbound";
132 session
.log("info", "Authenticated as %s@%s", username
, session
.host
or "(unknown)");
136 -- returns true, nil on success
137 -- returns nil, err_type, err, err_message on failure
138 local function bind_resource(session
, resource
)
139 if not session
.username
then return nil, "auth", "not-authorized", "Cannot bind resource before authentication"; end
140 if session
.resource
then return nil, "cancel", "not-allowed", "Cannot bind multiple resources on a single connection"; end
141 -- We don't support binding multiple resources
143 local event_payload
= { session
= session
, resource
= resource
};
144 if hosts
[session
.host
].events
.fire_event("pre-resource-bind", event_payload
) == false then
145 local err
= event_payload
.error;
146 if err
then return nil, err
.type, err
.condition
, err
.text
; end
147 return nil, "cancel", "not-allowed";
149 -- In case a plugin wants to poke at it
150 resource
= event_payload
.resource
;
153 resource
= resourceprep(resource
);
154 resource
= resource
~= "" and resource
or generate_identifier();
155 --FIXME: Randomly-generated resources must be unique per-user, and never conflict with existing
157 if not hosts
[session
.host
].sessions
[session
.username
] then
158 local sessions
= { sessions
= {} };
159 hosts
[session
.host
].sessions
[session
.username
] = sessions
;
160 bare_sessions
[session
.username
..'@'..session
.host
] = sessions
;
162 local sessions
= hosts
[session
.host
].sessions
[session
.username
].sessions
;
163 if sessions
[resource
] then
165 local policy
= config_get(session
.host
, "conflict_resolve");
167 if policy
== "random" then
168 resource
= generate_identifier();
170 elseif policy
== "increment" then
171 increment
= true; -- TODO ping old resource
172 elseif policy
== "kick_new" then
173 return nil, "cancel", "conflict", "Resource already exists";
174 else -- if policy == "kick_old" then
175 sessions
[resource
]:close
{
176 condition
= "conflict";
177 text
= "Replaced by new connection";
179 if not next(sessions
) then
180 hosts
[session
.host
].sessions
[session
.username
] = { sessions
= sessions
};
181 bare_sessions
[session
.username
.."@"..session
.host
] = hosts
[session
.host
].sessions
[session
.username
];
184 if increment
and sessions
[resource
] then
186 while sessions
[resource
.."#"..count
] do
189 resource
= resource
.."#"..count
;
194 session
.resource
= resource
;
195 session
.full_jid
= session
.username
.. '@' .. session
.host
.. '/' .. resource
;
196 hosts
[session
.host
].sessions
[session
.username
].sessions
[resource
] = session
;
197 full_sessions
[session
.full_jid
] = session
;
198 if session
.type == "c2s_unbound" then
199 session
.type = "c2s";
203 session
.roster
, err
= rm_load_roster(session
.username
, session
.host
);
205 -- FIXME: Why is all this rollback down here, instead of just doing the roster test up above?
206 full_sessions
[session
.full_jid
] = nil;
207 hosts
[session
.host
].sessions
[session
.username
].sessions
[resource
] = nil;
208 session
.full_jid
= nil;
209 session
.resource
= nil;
210 if session
.type == "c2s" then
211 session
.type = "c2s_unbound";
213 if next(bare_sessions
[session
.username
..'@'..session
.host
].sessions
) == nil then
214 bare_sessions
[session
.username
..'@'..session
.host
] = nil;
215 hosts
[session
.host
].sessions
[session
.username
] = nil;
217 session
.log("error", "Roster loading failed: %s", err
);
218 return nil, "cancel", "internal-server-error", "Error loading roster";
221 hosts
[session
.host
].events
.fire_event("resource-bind", {session
=session
});
226 local function send_to_available_resources(username
, host
, stanza
)
227 local jid
= username
.."@"..host
;
229 local user
= bare_sessions
[jid
];
231 for _
, session
in pairs(user
.sessions
) do
232 if session
.presence
then
233 session
.send(stanza
);
241 local function send_to_interested_resources(username
, host
, stanza
)
242 local jid
= username
.."@"..host
;
244 local user
= bare_sessions
[jid
];
246 for _
, session
in pairs(user
.sessions
) do
247 if session
.interested
then
248 session
.send(stanza
);
257 new_session
= new_session
;
258 retire_session
= retire_session
;
259 destroy_session
= destroy_session
;
260 make_authenticated
= make_authenticated
;
261 bind_resource
= bind_resource
;
262 send_to_available_resources
= send_to_available_resources
;
263 send_to_interested_resources
= send_to_interested_resources
;