1 local jid_bare
= require
"util.jid".bare
;
2 local jid_resource
= require
"util.jid".resource
;
3 local resourceprep
= require
"util.encodings".stringprep
.resourceprep
;
4 local st
= require
"util.stanza";
5 local dataforms
= require
"util.dataforms";
7 local allow_unaffiliated
= module
:get_option_boolean("allow_unaffiliated_register", false);
9 local enforce_nick
= module
:get_option_boolean("enforce_registered_nickname", false);
11 -- reserved_nicks[nick] = jid
12 local function get_reserved_nicks(room
)
13 if room
._reserved_nicks
then
14 return room
._reserved_nicks
;
16 module
:log("debug", "Refreshing reserved nicks...");
17 local reserved_nicks
= {};
18 for jid
in room
:each_affiliation() do
19 local data
= room
._affiliation_data
[jid
];
20 local nick
= data
and data
.reserved_nickname
;
21 module
:log("debug", "Refreshed for %s: %s", jid
, nick
);
23 reserved_nicks
[nick
] = jid
;
26 room
._reserved_nicks
= reserved_nicks
;
27 return reserved_nicks
;
30 -- Returns the registered nick, if any, for a JID
31 -- Note: this is just the *nick* part, i.e. the resource of the in-room JID
32 local function get_registered_nick(room
, jid
)
33 local registered_data
= room
._affiliation_data
[jid
];
34 if not registered_data
then
37 return registered_data
.reserved_nickname
;
40 -- Returns the JID, if any, that registered a nick (not in-room JID)
41 local function get_registered_jid(room
, nick
)
42 local reserved_nicks
= get_reserved_nicks(room
);
43 return reserved_nicks
[nick
];
46 module
:hook("muc-set-affiliation", function (event
)
47 -- Clear reserved nick cache
48 event
.room
._reserved_nicks
= nil;
51 module
:add_feature("jabber:iq:register");
53 module
:hook("muc-disco#info", function (event
)
54 event
.reply
:tag("feature", { var
= "jabber:iq:register" }):up();
57 local registration_form
= dataforms
.new
{
58 { name
= "FORM_TYPE", type = "hidden", value
= "http://jabber.org/protocol/muc#register" },
59 { name
= "muc#register_roomnick", type = "text-single", label
= "Nickname"},
62 local function enforce_nick_policy(event
)
63 local origin
, stanza
= event
.origin
, event
.stanza
;
64 local room
= assert(event
.room
); -- FIXME
65 if not room
then return; end
67 -- Check if the chosen nickname is reserved
68 local requested_nick
= jid_resource(stanza
.attr
.to
);
69 local reserved_by
= get_registered_jid(room
, requested_nick
);
70 if reserved_by
and reserved_by
~= jid_bare(stanza
.attr
.from
) then
71 module
:log("debug", "%s attempted to use nick %s reserved by %s", stanza
.attr
.from
, requested_nick
, reserved_by
);
72 local reply
= st
.error_reply(stanza
, "cancel", "conflict"):up();
73 origin
.send(reply
:tag("x", {xmlns
= "http://jabber.org/protocol/muc"}));
77 -- Check if the occupant has a reservation they must use
79 local nick
= get_registered_nick(room
, jid_bare(stanza
.attr
.from
));
81 if event
.occupant
then
82 event
.occupant
.nick
= jid_bare(event
.occupant
.nick
) .. "/" .. nick
;
83 elseif event
.dest_occupant
.nick
~= jid_bare(event
.dest_occupant
.nick
) .. "/" .. nick
then
84 module
:log("debug", "Attempt by %s to join as %s, but their reserved nick is %s", stanza
.attr
.from
, requested_nick
, nick
);
85 local reply
= st
.error_reply(stanza
, "cancel", "not-acceptable"):up();
86 origin
.send(reply
:tag("x", {xmlns
= "http://jabber.org/protocol/muc"}));
93 module
:hook("muc-occupant-pre-join", enforce_nick_policy
);
94 module
:hook("muc-occupant-pre-change", enforce_nick_policy
);
96 -- Discovering Reserved Room Nickname
97 -- http://xmpp.org/extensions/xep-0045.html#reservednick
98 module
:hook("muc-disco#info/x-roomuser-item", function (event
)
99 local nick
= get_registered_nick(event
.room
, jid_bare(event
.stanza
.attr
.from
));
101 event
.reply
:tag("identity", { category
= "conference", type = "text", name
= nick
})
105 local function handle_register_iq(room
, origin
, stanza
)
106 local user_jid
= jid_bare(stanza
.attr
.from
)
107 local affiliation
= room
:get_affiliation(user_jid
);
108 if affiliation
== "outcast" then
109 origin
.send(st
.error_reply(stanza
, "auth", "forbidden"));
111 elseif not (affiliation
or allow_unaffiliated
) then
112 origin
.send(st
.error_reply(stanza
, "auth", "registration-required"));
115 local reply
= st
.reply(stanza
);
116 local registered_nick
= get_registered_nick(room
, user_jid
);
117 if stanza
.attr
.type == "get" then
118 reply
:query("jabber:iq:register");
119 if registered_nick
then
120 reply
:tag("registered"):up();
121 reply
:tag("username"):text(registered_nick
);
125 reply
:add_child(registration_form
:form());
126 else -- type == set -- handle registration form
127 local query
= stanza
.tags
[1];
128 if query
:get_child("remove") then
129 -- Remove "member" affiliation, but preserve if any other
130 local new_affiliation
= affiliation
~= "member" and affiliation
;
131 local ok
, err_type
, err_condition
= room
:set_affiliation(true, user_jid
, new_affiliation
, nil, false);
133 origin
.send(st
.error_reply(stanza
, err_type
, err_condition
));
139 local form_tag
= query
:get_child("x", "jabber:x:data");
140 local reg_data
= form_tag
and registration_form
:data(form_tag
);
142 origin
.send(st
.error_reply(stanza
, "modify", "bad-request", "Error in form"));
145 -- Is the nickname valid?
146 local desired_nick
= resourceprep(reg_data
["muc#register_roomnick"]);
147 if not desired_nick
then
148 origin
.send(st
.error_reply(stanza
, "modify", "bad-request", "Invalid Nickname"));
151 -- Is the nickname currently in use by another user?
152 local current_occupant
= room
:get_occupant_by_nick(room
.jid
.."/"..desired_nick
);
153 if current_occupant
and current_occupant
.bare_jid
~= user_jid
then
154 origin
.send(st
.error_reply(stanza
, "cancel", "conflict"));
157 -- Is the nickname currently reserved by another user?
158 local reserved_by
= get_registered_jid(room
, desired_nick
);
159 if reserved_by
and reserved_by
~= user_jid
then
160 origin
.send(st
.error_reply(stanza
, "cancel", "conflict"));
165 -- Kick any sessions that are not using this nick before we register it
166 local required_room_nick
= room
.jid
.."/"..desired_nick
;
167 for room_nick
, occupant
in room
:each_occupant() do
168 if occupant
.bare_jid
== user_jid
and room_nick
~= required_room_nick
then
169 room
:set_role(true, room_nick
, nil); -- Kick (TODO: would be nice to use 333 code)
174 -- Checks passed, save the registration
175 if registered_nick
~= desired_nick
then
176 local registration_data
= { reserved_nickname
= desired_nick
};
177 local ok
, err_type
, err_condition
= room
:set_affiliation(true, user_jid
, affiliation
or "member", nil, registration_data
);
179 origin
.send(st
.error_reply(stanza
, err_type
, err_condition
));
182 module
:log("debug", "Saved nick registration for %s: %s", user_jid
, desired_nick
);
192 get_registered_nick
= get_registered_nick
;
193 get_registered_jid
= get_registered_jid
;
194 handle_register_iq
= handle_register_iq
;