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