128 lines
3.9 KiB
Lua
128 lines
3.9 KiB
Lua
|
--[[
|
||
|
Implements some utilities for accounts in an smr-style database.
|
||
|
|
||
|
# Lists all author_id, author_name in the database
|
||
|
lua tools/accounts/main.lua ls
|
||
|
|
||
|
# Resets the author 13 with a newly generated passfile, and writes the newly
|
||
|
# generated file to "new.passfile"
|
||
|
lua tools/accounts/main.lua -o new.passfile reset_password 13
|
||
|
|
||
|
# Deletes the author with id 40, all posts and comments by this author will
|
||
|
# also be deleted.
|
||
|
lua tools/accounts/main.lua delete 40
|
||
|
]]
|
||
|
local function sha3(data)
|
||
|
local tmpfn = os.tmpname()
|
||
|
local tmpf = assert(io.open(tmpfn,"wb"))
|
||
|
assert(tmpf:write(data))
|
||
|
assert(tmpf:close())
|
||
|
local pd = assert(io.popen("openssl dgst -sha3-512 " .. tmpfn, "r"))
|
||
|
local hex = pd:read("*a")
|
||
|
--[[
|
||
|
hex looks like
|
||
|
SHA3-512(/tmp/lua_qQtQu4)= 9ece086e9bac491fac5c1d1046ca11d737b92a2b2ebd93f005d7b710110c0a678288166e7fbe796883a4f2e9b3ca9f484f521d0ce464345cc1aec96779149c14
|
||
|
]]
|
||
|
hex = hex:match("= (%x+)%s*$")
|
||
|
assert(pd:close())
|
||
|
local ret_data = {}
|
||
|
for hex_byte in hex:gmatch("%x%x") do
|
||
|
table.insert(ret_data,string.char(tonumber(hex_byte,16)))
|
||
|
end
|
||
|
return table.concat(ret_data)
|
||
|
end
|
||
|
local sql = require("lsqlite3")
|
||
|
local argparse = require("argparse")
|
||
|
|
||
|
local parser = argparse(){
|
||
|
name = "tools/accounts/main.lua",
|
||
|
description = "View, reset, and delete accounts",
|
||
|
epilog = "Other tools included in the smr source distribution include:archive",
|
||
|
}
|
||
|
parser:help_max_width(80)
|
||
|
parser:option("-d --database","The database file","kore_chroot/data/posts.db")
|
||
|
parser:option("-o --output","Output to file (instead of stdout)")
|
||
|
local ls = parser:command("ls") {
|
||
|
description = "lists all user_id's and user_names in the database"
|
||
|
}
|
||
|
local reset_password = parser:command("reset_password") {
|
||
|
description = "Resets a password for a user, pass either an account id or an account name. If the inputed string can be converted to an account_id, and the account_id exists, it is considered an account_id, even if there is a user by the same name if it were considered an account_name."
|
||
|
}
|
||
|
reset_password:mutex(
|
||
|
reset_password:argument("account_id_or_name","The accout id or account name to reset")
|
||
|
)
|
||
|
local delete = parser:command("delete") {
|
||
|
description = "Deletes an account from the system. All posts and comments by this user are deleted as well. Once deleted, another user may claim the same username."
|
||
|
}
|
||
|
delete:mutex(
|
||
|
delete:argument("account_id","The accout id to reset"),
|
||
|
delete:argument("account_name","The name of the account to reset")
|
||
|
)
|
||
|
|
||
|
|
||
|
args = parser:parse()
|
||
|
local db,err,errmsg = sql.open(args.database, sql.OPEN_READWRITE)
|
||
|
if not db then
|
||
|
error(string.format("Failed to open %s : %s",args.database,errmsg))
|
||
|
end
|
||
|
if args.output then
|
||
|
io.stdout = assert(io.open(args.output,"w"))
|
||
|
end
|
||
|
if args.ls then
|
||
|
for row in db:rows("SELECT id, name FROM authors;") do
|
||
|
local id,name = unpack(row)
|
||
|
io.stdout:write(string.format("%d\t%s\n",id,name))
|
||
|
end
|
||
|
end
|
||
|
if args.reset_password then
|
||
|
local rngf = assert(io.open("/dev/urandom","rb"))
|
||
|
local passlength = string.byte(rngf:read(1)) + 64
|
||
|
local salt = rngf:read(64)
|
||
|
local password = rngf:read(passlength)
|
||
|
local authorid, authorname
|
||
|
authorid = tonumber(args.account_id_or_name)
|
||
|
if authorid == nil then --could not be converted to a number
|
||
|
authorname = args.account_id_or_name
|
||
|
end
|
||
|
rngf:close()
|
||
|
|
||
|
assert(io.stdout:write(password))
|
||
|
local hash = sha3(salt .. password)
|
||
|
local stmt_update_pass
|
||
|
if authorid then
|
||
|
stmt_update_pass = db:prepare([[
|
||
|
UPDATE authors
|
||
|
SET
|
||
|
salt = :salt,
|
||
|
passhash = :hash
|
||
|
WHERE
|
||
|
id=:authorid;]]
|
||
|
)
|
||
|
stmt_update_pass:bind(3,authorid)
|
||
|
else
|
||
|
stmt_update_pass = db:prepare([[
|
||
|
UPDATE authors
|
||
|
SET
|
||
|
salt = :salt,
|
||
|
passhash = :hash
|
||
|
WHERE
|
||
|
name=:authorname;]]
|
||
|
)
|
||
|
stmt_update_pass:bind(3,authorname)
|
||
|
end
|
||
|
|
||
|
stmt_update_pass:bind_blob(1,salt)
|
||
|
stmt_update_pass:bind_blob(2,hash)
|
||
|
local err = stmt_update_pass:step()
|
||
|
if err ~= sql.DONE then
|
||
|
io.stderr:write(string.format("Failed %d:%s",err,db:errmsg()))
|
||
|
end
|
||
|
end
|
||
|
|
||
|
db:close()
|
||
|
--[[
|
||
|
for k,v in pairs(args) do
|
||
|
print(k,":",v)
|
||
|
end
|
||
|
]]
|