smr/tools/accounts/main.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
]]