init.lua 8.3 KB

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