| 1 | -- Prosody IM |
| 2 | -- Copyright (C) 2008-2010 Matthew Wild |
| 3 | -- Copyright (C) 2008-2010 Waqas Hussain |
| 4 | -- |
| 5 | -- This project is MIT/X11 licensed. Please see the |
| 6 | -- COPYING file in the source package for more information. |
| 7 | -- |
| 8 | |
| 9 | |
| 10 | local hosts = _G.hosts; |
| 11 | local st = require "util.stanza"; |
| 12 | local datamanager = require "util.datamanager"; |
| 13 | local usermanager_user_exists = require "core.usermanager".user_exists; |
| 14 | local usermanager_create_user = require "core.usermanager".create_user; |
| 15 | local usermanager_set_password = require "core.usermanager".set_password; |
| 16 | local os_time = os.time; |
| 17 | local nodeprep = require "util.encodings".stringprep.nodeprep; |
| 18 | |
| 19 | module:add_feature("jabber:iq:register"); |
| 20 | |
| 21 | module:hook("iq/self/jabber:iq:register:query", function(event) |
| 22 | local session, stanza = event.origin, event.stanza; |
| 23 | |
| 24 | local query = stanza.tags[1]; |
| 25 | if stanza.attr.type == "get" then |
| 26 | local reply = st.reply(stanza); |
| 27 | reply:tag("query", {xmlns = "jabber:iq:register"}) |
| 28 | :tag("registered"):up() |
| 29 | :tag("username"):text(session.username):up() |
| 30 | :tag("password"):up(); |
| 31 | session.send(reply); |
| 32 | else -- stanza.attr.type == "set" |
| 33 | if query.tags[1] and query.tags[1].name == "remove" then |
| 34 | -- TODO delete user auth data, send iq response, kick all user resources with a <not-authorized/>, delete all user data |
| 35 | local username, host = session.username, session.host; |
| 36 | --session.send(st.error_reply(stanza, "cancel", "not-allowed")); |
| 37 | --return; |
| 38 | usermanager_set_password(username, nil, host); -- Disable account |
| 39 | -- FIXME the disabling currently allows a different user to recreate the account |
| 40 | -- we should add an in-memory account block mode when we have threading |
| 41 | session.send(st.reply(stanza)); |
| 42 | local roster = session.roster; |
| 43 | for _, session in pairs(hosts[host].sessions[username].sessions) do -- disconnect all resources |
| 44 | session:close({condition = "not-authorized", text = "Account deleted"}); |
| 45 | end |
| 46 | -- TODO datamanager should be able to delete all user data itself |
| 47 | datamanager.store(username, host, "vcard", nil); |
| 48 | datamanager.store(username, host, "private", nil); |
| 49 | datamanager.list_store(username, host, "offline", nil); |
| 50 | local bare = username.."@"..host; |
| 51 | for jid, item in pairs(roster) do |
| 52 | if jid and jid ~= "pending" then |
| 53 | if item.subscription == "both" or item.subscription == "from" or (roster.pending and roster.pending[jid]) then |
| 54 | core_post_stanza(hosts[host], st.presence({type="unsubscribed", from=bare, to=jid})); |
| 55 | end |
| 56 | if item.subscription == "both" or item.subscription == "to" or item.ask then |
| 57 | core_post_stanza(hosts[host], st.presence({type="unsubscribe", from=bare, to=jid})); |
| 58 | end |
| 59 | end |
| 60 | end |
| 61 | datamanager.store(username, host, "roster", nil); |
| 62 | datamanager.store(username, host, "privacy", nil); |
| 63 | datamanager.store(username, host, "accounts", nil); -- delete accounts datastore at the end |
| 64 | module:log("info", "User removed their account: %s@%s", username, host); |
| 65 | module:fire_event("user-deregistered", { username = username, host = host, source = "mod_register", session = session }); |
| 66 | else |
| 67 | local username = query:child_with_name("username"); |
| 68 | local password = query:child_with_name("password"); |
| 69 | if username and password then |
| 70 | -- FIXME shouldn't use table.concat |
| 71 | username = nodeprep(table.concat(username)); |
| 72 | password = table.concat(password); |
| 73 | if username == session.username then |
| 74 | if usermanager_set_password(username, password, session.host) then |
| 75 | session.send(st.reply(stanza)); |
| 76 | else |
| 77 | -- TODO unable to write file, file may be locked, etc, what's the correct error? |
| 78 | session.send(st.error_reply(stanza, "wait", "internal-server-error")); |
| 79 | end |
| 80 | else |
| 81 | session.send(st.error_reply(stanza, "modify", "bad-request")); |
| 82 | end |
| 83 | else |
| 84 | session.send(st.error_reply(stanza, "modify", "bad-request")); |
| 85 | end |
| 86 | end |
| 87 | end |
| 88 | return true; |
| 89 | end); |
| 90 | |
| 91 | local recent_ips = {}; |
| 92 | local min_seconds_between_registrations = module:get_option("min_seconds_between_registrations"); |
| 93 | local whitelist_only = module:get_option("whitelist_registration_only"); |
| 94 | local whitelisted_ips = module:get_option("registration_whitelist") or { "127.0.0.1" }; |
| 95 | local blacklisted_ips = module:get_option("registration_blacklist") or {}; |
| 96 | |
| 97 | for _, ip in ipairs(whitelisted_ips) do whitelisted_ips[ip] = true; end |
| 98 | for _, ip in ipairs(blacklisted_ips) do blacklisted_ips[ip] = true; end |
| 99 | |
| 100 | module:hook("stanza/iq/jabber:iq:register:query", function(event) |
| 101 | local session, stanza = event.origin, event.stanza; |
| 102 | |
| 103 | if module:get_option("allow_registration") == false or session.type ~= "c2s_unauthed" then |
| 104 | session.send(st.error_reply(stanza, "cancel", "service-unavailable")); |
| 105 | else |
| 106 | local query = stanza.tags[1]; |
| 107 | if stanza.attr.type == "get" then |
| 108 | local reply = st.reply(stanza); |
| 109 | reply:tag("query", {xmlns = "jabber:iq:register"}) |
| 110 | :tag("instructions"):text("Choose a username and password for use with this service."):up() |
| 111 | :tag("username"):up() |
| 112 | :tag("password"):up(); |
| 113 | session.send(reply); |
| 114 | elseif stanza.attr.type == "set" then |
| 115 | if query.tags[1] and query.tags[1].name == "remove" then |
| 116 | session.send(st.error_reply(stanza, "auth", "registration-required")); |
| 117 | else |
| 118 | local username = query:child_with_name("username"); |
| 119 | local password = query:child_with_name("password"); |
| 120 | if username and password then |
| 121 | -- Check that the user is not blacklisted or registering too often |
| 122 | if not session.ip then |
| 123 | module:log("debug", "User's IP not known; can't apply blacklist/whitelist"); |
| 124 | elseif blacklisted_ips[session.ip] or (whitelist_only and not whitelisted_ips[session.ip]) then |
| 125 | session.send(st.error_reply(stanza, "cancel", "not-acceptable", "You are not allowed to register an account.")); |
| 126 | return true; |
| 127 | elseif min_seconds_between_registrations and not whitelisted_ips[session.ip] then |
| 128 | if not recent_ips[session.ip] then |
| 129 | recent_ips[session.ip] = { time = os_time(), count = 1 }; |
| 130 | else |
| 131 | local ip = recent_ips[session.ip]; |
| 132 | ip.count = ip.count + 1; |
| 133 | |
| 134 | if os_time() - ip.time < min_seconds_between_registrations then |
| 135 | ip.time = os_time(); |
| 136 | session.send(st.error_reply(stanza, "wait", "not-acceptable")); |
| 137 | return true; |
| 138 | end |
| 139 | ip.time = os_time(); |
| 140 | end |
| 141 | end |
| 142 | -- FIXME shouldn't use table.concat |
| 143 | username = nodeprep(table.concat(username)); |
| 144 | password = table.concat(password); |
| 145 | local host = module.host; |
| 146 | if not username or username == "" then |
| 147 | session.send(st.error_reply(stanza, "modify", "not-acceptable", "The requested username is invalid.")); |
| 148 | elseif usermanager_user_exists(username, host) then |
| 149 | session.send(st.error_reply(stanza, "cancel", "conflict", "The requested username already exists.")); |
| 150 | else |
| 151 | if usermanager_create_user(username, password, host) then |
| 152 | session.send(st.reply(stanza)); -- user created! |
| 153 | module:log("info", "User account created: %s@%s", username, host); |
| 154 | module:fire_event("user-registered", { |
| 155 | username = username, host = host, source = "mod_register", |
| 156 | session = session }); |
| 157 | else |
| 158 | -- TODO unable to write file, file may be locked, etc, what's the correct error? |
| 159 | session.send(st.error_reply(stanza, "wait", "internal-server-error", "Failed to write data to disk.")); |
| 160 | end |
| 161 | end |
| 162 | else |
| 163 | session.send(st.error_reply(stanza, "modify", "not-acceptable")); |
| 164 | end |
| 165 | end |
| 166 | end |
| 167 | end |
| 168 | return true; |
| 169 | end); |