Builtin: Disallow registering users with the same name
[oweals/minetest.git] / builtin / game / auth.lua
1 -- Minetest: builtin/auth.lua
2
3 --
4 -- Authentication handler
5 --
6
7 function core.string_to_privs(str, delim)
8         assert(type(str) == "string")
9         delim = delim or ','
10         local privs = {}
11         for _, priv in pairs(string.split(str, delim)) do
12                 privs[priv:trim()] = true
13         end
14         return privs
15 end
16
17 function core.privs_to_string(privs, delim)
18         assert(type(privs) == "table")
19         delim = delim or ','
20         local list = {}
21         for priv, bool in pairs(privs) do
22                 if bool then
23                         list[#list + 1] = priv
24                 end
25         end
26         return table.concat(list, delim)
27 end
28
29 assert(core.string_to_privs("a,b").b == true)
30 assert(core.privs_to_string({a=true,b=true}) == "a,b")
31
32 core.auth_file_path = core.get_worldpath().."/auth.txt"
33 core.auth_table = {}
34
35 local function read_auth_file()
36         local newtable = {}
37         local file, errmsg = io.open(core.auth_file_path, 'rb')
38         if not file then
39                 core.log("info", core.auth_file_path.." could not be opened for reading ("..errmsg.."); assuming new world")
40                 return
41         end
42         for line in file:lines() do
43                 if line ~= "" then
44                         local fields = line:split(":", true)
45                         local name, password, privilege_string, last_login = unpack(fields)
46                         last_login = tonumber(last_login)
47                         if not (name and password and privilege_string) then
48                                 error("Invalid line in auth.txt: "..dump(line))
49                         end
50                         local privileges = core.string_to_privs(privilege_string)
51                         newtable[name] = {password=password, privileges=privileges, last_login=last_login}
52                 end
53         end
54         io.close(file)
55         core.auth_table = newtable
56         core.notify_authentication_modified()
57 end
58
59 local function save_auth_file()
60         local newtable = {}
61         -- Check table for validness before attempting to save
62         for name, stuff in pairs(core.auth_table) do
63                 assert(type(name) == "string")
64                 assert(name ~= "")
65                 assert(type(stuff) == "table")
66                 assert(type(stuff.password) == "string")
67                 assert(type(stuff.privileges) == "table")
68                 assert(stuff.last_login == nil or type(stuff.last_login) == "number")
69         end
70         local file, errmsg = io.open(core.auth_file_path, 'w+b')
71         if not file then
72                 error(core.auth_file_path.." could not be opened for writing: "..errmsg)
73         end
74         for name, stuff in pairs(core.auth_table) do
75                 local priv_string = core.privs_to_string(stuff.privileges)
76                 local parts = {name, stuff.password, priv_string, stuff.last_login or ""}
77                 file:write(table.concat(parts, ":").."\n")
78         end
79         io.close(file)
80 end
81
82 read_auth_file()
83
84 core.builtin_auth_handler = {
85         get_auth = function(name)
86                 assert(type(name) == "string")
87                 -- Figure out what password to use for a new player (singleplayer
88                 -- always has an empty password, otherwise use default, which is
89                 -- usually empty too)
90                 local new_password_hash = ""
91                 -- If not in authentication table, return nil
92                 if not core.auth_table[name] then
93                         return nil
94                 end
95                 -- Figure out what privileges the player should have.
96                 -- Take a copy of the privilege table
97                 local privileges = {}
98                 for priv, _ in pairs(core.auth_table[name].privileges) do
99                         privileges[priv] = true
100                 end
101                 -- If singleplayer, give all privileges except those marked as give_to_singleplayer = false
102                 if core.is_singleplayer() then
103                         for priv, def in pairs(core.registered_privileges) do
104                                 if def.give_to_singleplayer then
105                                         privileges[priv] = true
106                                 end
107                         end
108                 -- For the admin, give everything
109                 elseif name == core.setting_get("name") then
110                         for priv, def in pairs(core.registered_privileges) do
111                                 privileges[priv] = true
112                         end
113                 end
114                 -- All done
115                 return {
116                         password = core.auth_table[name].password,
117                         privileges = privileges,
118                         -- Is set to nil if unknown
119                         last_login = core.auth_table[name].last_login,
120                 }
121         end,
122         create_auth = function(name, password)
123                 assert(type(name) == "string")
124                 assert(type(password) == "string")
125                 core.log('info', "Built-in authentication handler adding player '"..name.."'")
126                 core.auth_table[name] = {
127                         password = password,
128                         privileges = core.string_to_privs(core.setting_get("default_privs")),
129                         last_login = os.time(),
130                 }
131                 save_auth_file()
132         end,
133         set_password = function(name, password)
134                 assert(type(name) == "string")
135                 assert(type(password) == "string")
136                 if not core.auth_table[name] then
137                         core.builtin_auth_handler.create_auth(name, password)
138                 else
139                         core.log('info', "Built-in authentication handler setting password of player '"..name.."'")
140                         core.auth_table[name].password = password
141                         save_auth_file()
142                 end
143                 return true
144         end,
145         set_privileges = function(name, privileges)
146                 assert(type(name) == "string")
147                 assert(type(privileges) == "table")
148                 if not core.auth_table[name] then
149                         core.builtin_auth_handler.create_auth(name,
150                                 core.get_password_hash(name,
151                                         core.setting_get("default_password")))
152                 end
153                 core.auth_table[name].privileges = privileges
154                 core.notify_authentication_modified(name)
155                 save_auth_file()
156         end,
157         reload = function()
158                 read_auth_file()
159                 return true
160         end,
161         record_login = function(name)
162                 assert(type(name) == "string")
163                 assert(core.auth_table[name]).last_login = os.time()
164                 save_auth_file()
165         end,
166 }
167
168 function core.register_authentication_handler(handler)
169         if core.registered_auth_handler then
170                 error("Add-on authentication handler already registered by "..core.registered_auth_handler_modname)
171         end
172         core.registered_auth_handler = handler
173         core.registered_auth_handler_modname = core.get_current_modname()
174         handler.mod_origin = core.registered_auth_handler_modname
175 end
176
177 function core.get_auth_handler()
178         return core.registered_auth_handler or core.builtin_auth_handler
179 end
180
181 local function auth_pass(name)
182         return function(...)
183                 local auth_handler = core.get_auth_handler()
184                 if auth_handler[name] then
185                         return auth_handler[name](...)
186                 end
187                 return false
188         end
189 end
190
191 core.set_player_password = auth_pass("set_password")
192 core.set_player_privs    = auth_pass("set_privileges")
193 core.auth_reload         = auth_pass("reload")
194
195
196 local record_login = auth_pass("record_login")
197
198 core.register_on_joinplayer(function(player)
199         record_login(player:get_player_name())
200 end)
201
202 core.register_on_prejoinplayer(function(name, ip)
203         local auth = core.auth_table
204         if auth[name] ~= nil then
205                 return
206         end
207
208         local name_lower = name:lower()
209         for k in pairs(auth) do
210                 if k:lower() == name_lower then
211                         return string.format("\nCannot create new player called '%s'. "..
212                                         "Another account called '%s' is already registered. "..
213                                         "Please check the spelling if it's your account "..
214                                         "or use a different nickname.", name, k)
215                 end
216         end
217 end)