- Code: Select all
% ls auth.txt
-rw-rw-r-- 1 minetest minetest 44712993 Feb 10 15:53 auth.txt
% wc -l auth.txt
110022 auth.txt
Needless to say, a flat file database of this size is going to be horribly inefficient. But even more concerning is the decision to use the SHA1 algorithm for user passwords, which is overkill for any computer game. A single hash including salt consists of 369 BYTES of data. Here is the record for my account "publicworks":
- Code: Select all
% grep publicworks auth.txt
publicworks:#1#+OriAAAAAAAAAAAAAAAAAA#fv92vwRTpxQYffOQdaTdl6CYzWnrCTTRJV3RQWvUpYHBSXwnwJAGmjRFMUsozLRwUvKONgwDyCH5S9Fs/VWhtzzG30uNVfNORaduM9u1Oo9IRLSMe9WH1FlxIXNh+NTluWeSE93mTmrg+POIVsYvfZBBVKeZoFtVJrmIDqnJxd8s8LOoc8FU0eg1HFTx0NUtgVBasPY5AJsIDqkzNkjsva9QFMfSy9YmNTYasbNuotDAG7DmY2iOQpsDZUAR5HdZGvcxDr+d+uKpVPNod3BxbBHTvpZlACXTMbHUt97Nrs589lKU9W2X6TOq48zIK9GjAoT7SuAIiskg/pjV6NCX3g:water,interact,shout,lava,basic_privs:1482206476
Now, imagine storing passwords for hundreds of thousands of accounts just like this one. Since most users probably only have passwords consisting of 5-8 characters, the resulting hash is 40 TIMES longer than the original plain-text string. That is unjustifiably excessive.
Of course, the file format is only partially to blame. It is the builtin authentication handler that is the real problem. Check this out: Whenever a new user joins a server, create_auth( ) is automatically invoked:
- Code: Select all
create_auth = function(name, password)
minetest.log( "action", "Adding password entry for player " .. name )
assert(type(name) == "string")
assert(type(password) == "string")
core.log('info', "Built-in authentication handler adding player '"..name.."'")
core.auth_table[name] = {
password = password,
privileges = core.string_to_privs(core.setting_get("default_privs")),
last_login = os.time(),
}
save_auth_file()
end,
Immediately afterward, record_login( ) is invoked to update the login timestamp, which had just been initialized by the previous function:
- Code: Select all
record_login = function(name)
assert(type(name) == "string")
assert(core.auth_table[name]).last_login = os.time()
save_auth_file()
end,
Notice the senseless redundancy. Together these functions end up rewriting the exact same 110,000 lines of text, TWO TIMES OVER in the case of new users, causing the server thread to hang almost indefinitely, particularly if there is a growing queue of new players logging in. I think it's concerning how such poor quality code ever made it past inspection, and even moreso why it persisted for well over a year through multiple version changes.
Numerous high-traffic servers including Extreme Survival Minetest Server, Just Test II, Cash's World, and undoubtedly countless others have suffered due to this issue being unresolved. Most server operators probably just rack it up to an operating system deficiency, a faulty hard drive, lack of memory, or some other external cause.
Clearly something has to change with the builtin authentication handler, because this is an absolutely unacceptable implementation for any type of production environment. I've already got an interim fix in place on my own server. I disabled all writing and reading of the auth.txt file until startup and shutdown via a new auth.commit( ) function or upon operator request using an "auth_commit" chat command. After deploying this fix, the lag dropped from upwards of 10 seconds to a mere 0.1 seconds. That's a 100x performance improvement.
All of the patched files are included as an attachment, with a readme describing the issue and a resolution.
I really hope the core developer team will re-examine their existing quality control measures, because critical bugs like this (and even this) should have never made their way into a "stable" build.