init.lua 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. -- sauth mod for minetest voxel game
  2. -- by shivajiva101@hotmail.com
  3. -- Expose auth handler functions
  4. sauth = {}
  5. local auth_table = {}
  6. local MN = minetest.get_current_modname()
  7. local WP = minetest.get_worldpath()
  8. local ie = minetest.request_insecure_environment()
  9. if not ie then
  10. error("insecure environment inaccessible"..
  11. " - make sure this mod has been added to minetest.conf!")
  12. end
  13. -- Requires library for db access
  14. local _sql = ie.require("lsqlite3")
  15. -- Don't allow other mods to use this global library!
  16. if sqlite3 then sqlite3 = nil end
  17. local singleplayer = minetest.is_singleplayer()
  18. -- Use conf setting to determine handler for singleplayer
  19. if not minetest.settings:get(MN .. '.enable_singleplayer') and singleplayer then
  20. minetest.log("info", "singleplayer game using builtin auth handler")
  21. return
  22. end
  23. local db = _sql.open(WP.."/sauth.sqlite") -- connection
  24. -- Create db:exec wrapper for error reporting
  25. local function db_exec(stmt)
  26. if db:exec(stmt) ~= _sql.OK then
  27. minetest.log("error", "Sqlite ERROR: ", db:errmsg())
  28. end
  29. end
  30. local function cache_check(name)
  31. local chk = false
  32. for _,data in ipairs(minetest.get_connected_players()) do
  33. if data:get_player_name() == name then
  34. chk = true
  35. break
  36. end
  37. end
  38. if not chk then
  39. auth_table[name] = nil
  40. end
  41. end
  42. -- Db tables - because we need them!
  43. local create_db = [[
  44. CREATE TABLE IF NOT EXISTS auth (id INTEGER PRIMARY KEY AUTOINCREMENT,
  45. name VARCHAR(32), password VARCHAR(512), privileges VARCHAR(512),
  46. last_login INTEGER);
  47. CREATE TABLE IF NOT EXISTS _s (import BOOLEAN);
  48. ]]
  49. db_exec(create_db)
  50. --[[
  51. ###########################
  52. ### Database: Queries ###
  53. ###########################
  54. ]]
  55. local function get_record(name)
  56. local query = ([[
  57. SELECT * FROM auth WHERE name = '%s' LIMIT 1;
  58. ]]):format(name)
  59. for row in db:nrows(query) do
  60. return row
  61. end
  62. end
  63. local function check_name(name)
  64. local query = ([[
  65. SELECT DISTINCT name
  66. FROM auth
  67. WHERE LOWER(name) = LOWER('%s') LIMIT 1;
  68. ]]):format(name)
  69. for row in db:nrows(query) do
  70. return row
  71. end
  72. end
  73. local function check_name_all(name)
  74. local query = ([[
  75. SELECT name
  76. FROM auth
  77. WHERE LOWER(name) = LOWER('%s');
  78. ]]):format(name)
  79. local t = {}
  80. for row in db:nrows(query) do
  81. t[#t+1] = row.name
  82. end
  83. return t
  84. end
  85. local function get_setting(column)
  86. local query = ([[
  87. SELECT %s FROM _s
  88. ]]):format(column)
  89. for row in db:nrows(query) do
  90. return row
  91. end
  92. end
  93. local function get_names(name)
  94. local r,q = {}
  95. q = "SELECT name FROM auth WHERE name LIKE '%"..name.."%';"
  96. for row in db:nrows(q) do
  97. r[#r+1] = row.name
  98. end
  99. return r
  100. end
  101. --[[
  102. ##############################
  103. ### Database: Statements ###
  104. ##############################
  105. ]]
  106. local function add_record(name, password, privs, last_login)
  107. local stmt = ([[
  108. INSERT INTO auth (
  109. name,
  110. password,
  111. privileges,
  112. last_login
  113. ) VALUES ('%s','%s','%s','%s')
  114. ]]):format(name, password, privs, last_login)
  115. db_exec(stmt)
  116. end
  117. local function add_setting(column, val)
  118. local stmt = ([[
  119. INSERT INTO _s (%s) VALUES ('%s')
  120. ]]):format(column, val)
  121. db_exec(stmt)
  122. end
  123. local function update_login(name)
  124. local ts = os.time()
  125. local stmt = ([[
  126. UPDATE auth SET last_login = %i WHERE name = '%s'
  127. ]]):format(ts, name)
  128. db_exec(stmt)
  129. end
  130. local function update_password(name, password)
  131. local stmt = ([[
  132. UPDATE auth SET password = '%s' WHERE name = '%s'
  133. ]]):format(password,name)
  134. db_exec(stmt)
  135. end
  136. local function update_privileges(name, privs)
  137. local stmt = ([[
  138. UPDATE auth SET privileges = '%s' WHERE name = '%s'
  139. ]]):format(privs,name)
  140. db_exec(stmt)
  141. end
  142. local function del_record(name)
  143. local stmt = ([[
  144. DELETE FROM auth WHERE name = '%s'
  145. ]]):format(name)
  146. db_exec(stmt)
  147. end
  148. --[[
  149. ######################
  150. ### Auth Handler ###
  151. ######################
  152. ]]
  153. sauth.auth_handler = {
  154. get_auth = function(name, add_to_cache)
  155. -- Return password, privileges, last_login.
  156. assert(type(name) == 'string')
  157. -- Datch empty names for mods that do privilege checks.
  158. if name == '' or name == ' ' then
  159. minetest.log("info", "[sauth]: Name missing in call to get_auth. Rejected.")
  160. return nil
  161. end
  162. add_to_cache = add_to_cache or true -- Assert caching on missing param
  163. local r = auth_table[name]
  164. -- Check and load db record if reqd
  165. if r == nil then
  166. r = get_record(name)
  167. else
  168. return r -- cached copy
  169. end
  170. -- Return nil on missing entry
  171. if not r then return nil end
  172. -- Figure out what privileges the player should have.
  173. -- Take a copy of the players privilege table
  174. local privileges, admin = {}
  175. for priv, _ in pairs(minetest.string_to_privs(r.privileges)) do
  176. privileges[priv] = true
  177. end
  178. if core.settings then
  179. admin = core.settings:get("name")
  180. else
  181. admin = core.setting_get("name")
  182. end
  183. -- If singleplayer, grant privileges marked give_to_singleplayer = true
  184. if core.is_singleplayer() then
  185. for priv, def in pairs(core.registered_privileges) do
  186. if def.give_to_singleplayer then
  187. privileges[priv] = true
  188. end
  189. end
  190. -- If admin, grant all privileges
  191. elseif name == admin then
  192. for priv, def in pairs(core.registered_privileges) do
  193. privileges[priv] = true
  194. end
  195. end
  196. -- Construct record
  197. local record = {
  198. password = r.password,
  199. privileges = privileges,
  200. last_login = tonumber(r.last_login)
  201. }
  202. if not auth_table[name] and add_to_cache then auth_table[name] = record end -- Cache if reqd
  203. return record
  204. end,
  205. create_auth = function(name, password)
  206. assert(type(name) == 'string')
  207. assert(type(password) == 'string')
  208. local ts, privs = os.time()
  209. if core.settings then
  210. privs = core.settings:get("default_privs")
  211. else
  212. -- use old method
  213. privs = core.setting_get("default_privs")
  214. end
  215. -- Params: name, password, privs, last_login
  216. add_record(name,password,privs,ts)
  217. return true
  218. end,
  219. delete_auth = function(name)
  220. assert(type(name) == 'string')
  221. -- Offline only!
  222. if auth_table[name] == nil then del_record(name) end
  223. return true
  224. end,
  225. set_password = function(name, password)
  226. assert(type(name) == 'string')
  227. assert(type(password) == 'string')
  228. -- get player record
  229. if get_record(name) == nil then
  230. sauth.auth_handler.create_auth(name, password)
  231. else
  232. update_password(name, password)
  233. if auth_table[name] then auth_table[name].password = password end
  234. end
  235. return true
  236. end,
  237. set_privileges = function(name, privs)
  238. assert(type(name) == 'string')
  239. assert(type(privs) == 'table')
  240. if not sauth.auth_handler.get_auth(name) then
  241. -- create the record
  242. if core.settings then
  243. sauth.auth_handler.create_auth(name,
  244. core.get_password_hash(name,
  245. core.settings:get("default_password")))
  246. else
  247. sauth.auth_handler.create_auth(name,
  248. core.get_password_hash(name,
  249. core.setting_get("default_password")))
  250. end
  251. end
  252. local admin
  253. if core.settings then
  254. admin = core.settings:get("name")
  255. else
  256. admin = core.setting_get("name")
  257. end
  258. if name == admin then privs.privs = true end
  259. update_privileges(name, minetest.privs_to_string(privs))
  260. if auth_table[name] then auth_table[name].privileges = privs end
  261. minetest.notify_authentication_modified(name)
  262. return true
  263. end,
  264. reload = function()
  265. return true
  266. end,
  267. record_login = function(name)
  268. assert(type(name) == 'string')
  269. update_login(name)
  270. auth_table[name].last_login = os.time()
  271. return true
  272. end,
  273. name_search = function(name)
  274. assert(type(name) == 'string')
  275. return get_names(name)
  276. end,
  277. -- This function should scan through the DB and check if name already has a similar entry.
  278. check_similar_name = function(name)
  279. assert(type(name) == 'string')
  280. return check_name_all(name)
  281. end,
  282. }
  283. --[[
  284. ########################
  285. ### Register hooks ###
  286. ########################
  287. ]]
  288. -- Register auth handler
  289. minetest.register_authentication_handler(sauth.auth_handler)
  290. minetest.log('action', MN .. ": Registered auth handler")
  291. -- Housekeeping
  292. minetest.register_on_leaveplayer(function(player)
  293. -- Schedule a check to see if the player has gone
  294. minetest.after(60*3, cache_check, player:get_player_name())
  295. end)
  296. minetest.register_on_prejoinplayer(function(name, ip)
  297. local r = get_record(name)
  298. if r ~= nil then
  299. return
  300. end
  301. -- Check name isn't registered
  302. local chk = check_name(name)
  303. if chk then
  304. return ("\nCannot create new player called '%s'. "..
  305. "Another account called '%s' is already registered. "..
  306. "Please check the spelling if it's your account "..
  307. "or use a different nickname."):format(name, chk.name)
  308. end
  309. end)
  310. minetest.register_on_shutdown(function()
  311. db:close()
  312. end)