--[[ 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 ]]