auth.lua 6.1 KB

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