1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
|
local tonumber, tostring = tonumber, tostring;
local ipairs, pairs, print, next= ipairs, pairs, print, next;
local collectgarbage = collectgarbage;
local m_random = import("math", "random");
local format = import("string", "format");
local hosts = hosts;
local sessions = sessions;
local modulemanager = require "core.modulemanager";
local log = require "util.logger".init("sessionmanager");
local error = error;
local uuid_generate = require "util.uuid".generate;
local rm_load_roster = require "core.rostermanager".load_roster;
local newproxy = newproxy;
local getmetatable = getmetatable;
module "sessionmanager"
local open_sessions = 0;
function new_session(conn)
local session = { conn = conn, priority = 0, type = "c2s_unauthed" };
if true then
session.trace = newproxy(true);
getmetatable(session.trace).__gc = function () open_sessions = open_sessions - 1; print("Session got collected, now "..open_sessions.." sessions are allocated") end;
end
open_sessions = open_sessions + 1;
local w = conn.write;
session.send = function (t) w(tostring(t)); end
return session;
end
function destroy_session(session)
(session.log or log)("info", "Destroying session");
-- Send unavailable presence
if session.presence and session.presence.attr.type ~= "unavailable" then
local pres = st.presence{ type = "unavailable" };
if err == "closed" then err = "connection closed"; end
pres:tag("status"):text("Disconnected: "..err);
session.stanza_dispatch(pres);
end
-- Remove session/resource from user's session list
if session.host and session.username then
if session.resource then
hosts[session.host].sessions[session.username].sessions[session.resource] = nil;
end
if hosts[session.host] and hosts[session.host].sessions[session.username] then
if not next(hosts[session.host].sessions[session.username].sessions) then
log("debug", "All resources of %s are now offline", session.username);
hosts[session.host].sessions[session.username] = nil;
end
end
end
for k in pairs(session) do
if k ~= "trace" then
session[k] = nil;
end
end
end
function make_authenticated(session, username)
session.username = username;
if session.type == "c2s_unauthed" then
session.type = "c2s";
end
return true;
end
-- returns true, nil on success
-- returns nil, err_type, err, err_message on failure
function bind_resource(session, resource)
if not session.username then return nil, "auth", "not-authorized", "Cannot bind resource before authentication"; end
if session.resource then return nil, "cancel", "already-bound", "Cannot bind multiple resources on a single connection"; end
-- We don't support binding multiple resources
resource = resource or uuid_generate();
--FIXME: Randomly-generated resources must be unique per-user, and never conflict with existing
if not hosts[session.host].sessions[session.username] then
hosts[session.host].sessions[session.username] = { sessions = {} };
else
if hosts[session.host].sessions[session.username].sessions[resource] then
-- Resource conflict
return nil, "cancel", "conflict", "Resource already exists"; -- TODO kick old resource
end
end
session.resource = resource;
session.full_jid = session.username .. '@' .. session.host .. '/' .. resource;
hosts[session.host].sessions[session.username].sessions[resource] = session;
session.roster = rm_load_roster(session.username, session.host);
return true;
end
function streamopened(session, attr)
local send = session.send;
session.host = attr.to or error("Client failed to specify destination hostname");
session.version = tonumber(attr.version) or 0;
session.streamid = m_random(1000000, 99999999);
(session.log or session)("debug", "Client sent opening <stream:stream> to %s", session.host);
send("<?xml version='1.0'?>");
send(format("<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='%s' from='%s' version='1.0'>", session.streamid, session.host));
if not hosts[session.host] then
-- We don't serve this host...
session:disconnect{ condition = "host-unknown", text = "This server does not serve "..tostring(session.host)};
return;
end
local features = {};
modulemanager.fire_event("stream-features", session, features);
-- FIXME: Need to send() this all at once
send("<stream:features>");
for _, feature in ipairs(features) do
send(tostring(feature));
end
send("</stream:features>");
log("info", "Stream opened successfully");
session.notopen = nil;
end
function send_to_available_resources(user, host, stanza)
local count = 0;
local to = stanza.attr.to;
stanza.attr.to = nil;
local h = hosts[host];
if h and h.type == "local" then
local u = h.sessions[user];
if u then
for k, session in pairs(u.sessions) do
if session.presence then
session.send(stanza);
count = count + 1;
end
end
end
end
stanza.attr.to = to;
return count;
end
return _M;
|