This repository has been archived by the owner on Aug 20, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
init.lua
361 lines (339 loc) · 13.2 KB
/
init.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
local thismod = {
enabled = false
}
_G[modname] = thismod
local LogI = mysql_base.mklog('action', modname)
local LogE = mysql_base.mklog('error', modname)
if not mysql_base.enabled then
LogI("mysql_base disabled, not loading mod")
return
end
local singleplayer = minetest.is_singleplayer() -- Caching is OK since you can't open a game to
-- multiplayer unless you restart it.
if not minetest.settings:get(modname .. '.enable_singleplayer') and singleplayer then
LogI("Not adding auth handler because of singleplayer game")
return
end
thismod.enabled = true
local LogV = function() end
do
local get = mysql_base.mkget(modname)
if get('verbose') == 'true' then
LogI("Verbose logging enabled")
LogV = mysql_base.mklog('verbose', modname)
end
local conn, dbname = mysql_base.conn, mysql_base.dbname
local tables = {}
thismod.tables = tables
do -- Tables and schema settings
local t_auths = get('db.tables.auths')
if type(t_auths) == 'table' then
tables.auths = t_auths
else
tables.auths = {}
tables.auths.name = get('db.tables.auths.name')
tables.auths.schema = {}
local S = tables.auths.schema
S.userid = get('db.tables.auths.schema.userid')
S.username = get('db.tables.auths.schema.username')
S.password = get('db.tables.auths.schema.password')
S.privs = get('db.tables.auths.schema.privs')
S.lastlogin = get('db.tables.auths.schema.lastlogin')
S.userid_type = get('db.tables.auths.schema.userid_type')
S.username_type = get('db.tables.auths.schema.username_type')
S.password_type = get('db.tables.auths.schema.password_type')
S.privs_type = get('db.tables.auths.schema.privs_type')
S.lastlogin_type = get('db.tables.auths.schema.lastlogin_type')
end
do -- Default values
tables.auths.name = tables.auths.name or 'auths'
tables.auths.schema = tables.auths.schema or {}
local S = tables.auths.schema
S.userid = S.userid or 'userid'
S.username = S.username or 'username'
S.password = S.password or 'password'
S.privs = S.privs or 'privs'
S.lastlogin = S.lastlogin or 'lastlogin'
S.userid_type = S.userid_type or 'INT'
S.username_type = S.username_type or 'VARCHAR(32)'
S.password_type = S.password_type or 'VARCHAR(512)'
S.privs_type = S.privs_type or 'VARCHAR(512)'
S.lastlogin_type = S.lastlogin_type or 'BIGINT'
-- Note lastlogin doesn't use the TIMESTAMP type, which is 32-bit and therefore
-- subject to the year 2038 problem.
end
end
local auth_table_created
-- Auth table existence check and setup
if not mysql_base.table_exists(tables.auths.name) then
-- Auth table doesn't exist, create it
local S = tables.auths.schema
mysql_base.create_table(tables.auths.name, {
columns = {
{S.userid, S.userid_type, notnull = true, autoincrement = true},
{S.username, S.username_type, notnull = true},
{S.password, S.password_type, notnull = true},
{S.privs, S.privs_type, notnull = true},
{S.lastlogin, S.lastlogin_type},
},
pkey = {S.userid},
unique = {S.username},
})
LogI("Created table '" .. dbname .. "." .. tables.auths.name .. "'")
auth_table_created = true
end
local S = tables.auths.schema
local get_auth_stmt, get_auth_params, get_auth_results = mysql_base.prepare_select(
tables.auths.name, {
{S.userid, S.userid_type},
{S.password, S.password_type},
{S.privs, S.privs_type},
{S.lastlogin, S.lastlogin_type}},
S.username .. '=?', {S.username_type})
thismod.get_auth_stmt = get_auth_stmt
local create_auth_stmt, create_auth_params = mysql_base.prepare_insert(
tables.auths.name, {
{S.username, S.username_type},
{S.password, S.password_type},
{S.privs, S.privs_type},
{S.lastlogin, S.lastlogin_type},
})
thismod.create_auth_stmt = create_auth_stmt
thismod.create_auth_params = create_auth_params
local max_name_len = tonumber(create_auth_params.buffer[0].buffer_length)
local max_pass_len = tonumber(create_auth_params.buffer[1].buffer_length)
local delete_auth_stmt, delete_auth_params = mysql_base.prepare_delete(tables.auths.name,
S.username .. '=?', {S.username_type})
local set_password_stmt, set_password_params = mysql_base.prepare_update(tables.auths.name,
{{S.password, S.password_type}},
S.username .. '=?', {S.username_type})
local set_privileges_stmt, set_privileges_params = mysql_base.prepare_update(tables.auths.name,
{{S.privs, S.privs_type}},
S.username .. '=?', {S.username_type})
local max_privs_len = tonumber(set_privileges_params.buffer[0].buffer_length)
local record_login_stmt, record_login_params = mysql_base.prepare_update(tables.auths.name,
{{S.lastlogin, S.lastlogin_type}},
S.username .. '=?', {S.username_type})
local enumerate_auths_query = 'SELECT ' .. S.username .. ',' .. S.password .. ',' .. S.privs ..
',' .. S.lastlogin .. ' FROM ' .. tables.auths.name
thismod.enumerate_auths_query = enumerate_auths_query
if auth_table_created and get('import_auth_txt_on_table_create') == 'true' then
if not thismod.import_auth_txt then
dofile(modpath .. '/auth_txt_import.lua')
end
thismod.import_auth_txt()
end
thismod.auth_handler = {
get_auth = function(name)
assert(type(name) == 'string')
if name:len() > max_name_len then
LogE("get_auth(" .. name .. ") failed: name too long (max " .. max_name_len .. ")")
return nil
end
get_auth_params:set(1, name)
local success, msg = pcall(get_auth_stmt.exec, get_auth_stmt)
if not success then
LogE("get_auth(" .. name .. ") failed: " .. msg)
return nil
end
get_auth_stmt:store_result()
if not get_auth_stmt:fetch() then
-- No such auth row exists
return nil
end
while get_auth_stmt:fetch() do
error(modname .. ": get_auth(" .. name .. "): multiples lines were returned")
end
local userid, password, privs_str, lastlogin = get_auth_results:get(1),
get_auth_results:get(2), get_auth_results:get(3), get_auth_results:get(4)
local admin
if minetest.settings then
admin = (name == minetest.settings:get("name"))
else
admin = (name == minetest.setting_get("name"))
end
local privs
if singleplayer or admin then
privs = {}
-- If admin, grant all privs, if singleplayer, grant all privs w/ give_to_singleplayer
for priv, def in pairs(core.registered_privileges) do
if (singleplayer and def.give_to_singleplayer) or admin then
privs[priv] = true
end
end
if admin and not thismod.admin_get_auth_called then
thismod.admin_get_auth_called = true
thismod.auth_handler.set_privileges(name, privs)
end
else
privs = minetest.string_to_privs(privs_str)
end
LogV("get_auth(" .. name .. ") -> {userid:" .. userid .. ", privileges: " ..
table.concat(privs, ',') .. "}")
return {
userid = userid,
password = password,
privileges = privs,
last_login = tonumber(lastlogin)
}
end,
create_auth = function(name, password, reason)
assert(type(name) == 'string')
assert(type(password) == 'string')
LogV("create_auth(" .. name .. ", ###" .. (reason and (", " .. reason) or "") .. ")")
LogI("Creating player '" .. name .. "'" .. (reason or ""))
if name:len() > max_name_len then
LogE("create_auth(" .. name .. ") failed: name too long (max " .. max_name_len .. ")")
return false
end
if password:len() > max_pass_len then
LogE("create_auth(" .. name .. ") failed: password too long (max " .. max_pass_len .. ")")
return false
end
create_auth_params:set(1, name)
create_auth_params:set(2, password)
if minetest.settings then
create_auth_params:set(3, minetest.settings:get("default_privs"))
else
create_auth_params:set(3, minetest.setting_get("default_privs"))
end
create_auth_params:set(4, math.floor(os.time()))
local success, msg = pcall(create_auth_stmt.exec, create_auth_stmt)
if not success then
LogE("create_auth(" .. name .. ") failed: " .. msg)
return false
end
if create_auth_stmt:affected_rows() ~= 1 then
LogE("create_auth(" .. name .. ") failed: affected row count is " ..
create_auth_stmt:affected_rows() .. ", expected 1")
return false
end
return true
end,
delete_auth = function(name)
assert(type(name) == 'string')
LogV("delete_auth(" .. name .. ")")
LogI("Deleting player '"..name.."'")
if name:len() > max_name_len then
LogE("delete_auth(" .. name .. ") failed: name too long (max " .. max_name_len .. ")")
return false
end
delete_auth_params:set(1, name)
local success, msg = pcall(delete_auth_stmt.exec, delete_auth_stmt)
if not success then
LogE("delete_auth(" .. name .. ") failed: " .. msg)
return false
end
if delete_auth_stmt:affected_rows() ~= 1 then
LogE("delete_auth(" .. name .. ") failed: affected row count is " ..
delete_auth_stmt:affected_rows() .. ", expected 1")
return false
end
return true
end,
set_password = function(name, password)
assert(type(name) == 'string')
assert(type(password) == 'string')
LogV("set_password(" .. name .. ", ###)")
if name:len() > max_name_len then
LogE("create_auth(" .. name .. ") failed: name too long (max " .. max_name_len .. ")")
return false
end
if password:len() > max_pass_len then
LogE("create_auth(" .. name .. ") failed: password too long (max " .. max_pass_len .. ")")
return false
end
if not thismod.auth_handler.get_auth(name) then
return thismod.auth_handler.create_auth(name, password, " because set_password was requested")
else
LogI("Setting password of player '" .. name .. "'")
set_password_params:set(1, password)
set_password_params:set(2, name)
local success, msg = pcall(set_password_stmt.exec, set_password_stmt)
if not success then
LogE("set_password(" .. name .. ") failed: " .. msg)
return false
end
if set_password_stmt:affected_rows() ~= 1 then
LogE("set_password(" .. name .. ") failed: affected row count is " ..
set_password_stmt:affected_rows() .. ", expected 1")
return false
end
return true
end
end,
set_privileges = function(name, privileges)
assert(type(name) == 'string')
assert(type(privileges) == 'table')
local privstr = minetest.privs_to_string(privileges)
LogV("set_privileges(" .. name .. ", {" .. table.concat(privileges, ', ') .. "}) [" ..
privstr .. "]")
if name:len() > max_name_len then
LogE("set_privileges(" .. name .. ") failed: name too long (max " .. max_name_len .. ")")
return false
end
if privstr:len() > max_privs_len then
LogE("create_auth(" .. name .. ") failed: priv string too long (max " ..
max_privs_len .. ")")
return false
end
set_privileges_params:set(1, privstr)
set_privileges_params:set(2, name)
local success, msg = pcall(set_privileges_stmt.exec, set_privileges_stmt)
if not success then
LogE("set_privileges(" .. name .. ") failed: " .. msg)
return false
end
minetest.notify_authentication_modified(name)
return true
end,
reload = function()
return true
end,
record_login = function(name)
assert(type(name) == 'string')
LogV("record_login(" .. name .. ")")
if name:len() > max_name_len then
LogE("set_privileges(" .. name .. ") failed: name too long (max " .. max_name_len .. ")")
return false
end
record_login_params:set(1, math.floor(os.time()))
record_login_params:set(2, name)
local success, msg = pcall(record_login_stmt.exec, record_login_stmt)
if not success then
LogE("record_login(" .. name .. ") failed: " .. msg)
return false
end
if record_login_stmt:affected_rows() ~= 1 then
LogE("record_login(" .. name .. ") failed: affected row count is " ..
record_login_stmt:affected_rows() .. ", expected 1")
return false
end
return true
end,
enumerate_auths = function()
LogV("enumerate_auths()")
conn:query(enumerate_auths_query)
local res = conn:store_result()
return function()
local row = res:fetch('n')
if not row then
return nil
end
local username, password, privs_str, lastlogin = unpack(row)
return username, {
password = password,
privileges = minetest.string_to_privs(privs_str),
last_login = tonumber(lastlogin)
}
end
end
}
end
minetest.register_authentication_handler(thismod.auth_handler)
LogI("Registered auth handler")
mysql_base.register_on_shutdown(function()
thismod.get_auth_stmt:free_result()
end)